[OPENJPA-2567] various MySql and MariaDB text types support is added - thanks @solomax - This closes #18

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@1839940 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Francesco Chicchiriccò 2018-09-03 11:41:07 +00:00
parent 6c4c77aa78
commit 9a98d6a2bf
4 changed files with 258 additions and 49 deletions

View File

@ -14,7 +14,7 @@
* "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.
* under the License.
*/
package org.apache.openjpa.jdbc.sql;
@ -30,7 +30,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.identifier.DBIdentifier.DBIdentifierType;
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
@ -41,17 +40,18 @@ import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Index;
import org.apache.openjpa.jdbc.schema.PrimaryKey;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.util.StoreException;
/*
* Dictionary for MariaDB, based off the MySQLDictionary.
*
*
*/
public class MariaDBDictionary extends DBDictionary {
public static final String SELECT_HINT = "openjpa.hint.MariaDBSelectHint";
public static final String DELIMITER_BACK_TICK = "`";
/**
* The MySQL table type to use when creating tables; defaults to innodb.
*/
@ -69,8 +69,8 @@ public class MariaDBDictionary extends DBDictionary {
public boolean driverDeserializesBlobs = false;
/**
* Whether to inline multi-table bulk-delete operations into MySQL's
* combined <code>DELETE FROM foo, bar, baz</code> syntax.
* Whether to inline multi-table bulk-delete operations into MySQL's
* combined <code>DELETE FROM foo, bar, baz</code> syntax.
* Defaults to false, since this may fail in the presence of InnoDB tables
* with foreign keys.
* @see http://dev.mysql.com/doc/refman/5.0/en/delete.html
@ -80,6 +80,9 @@ public class MariaDBDictionary extends DBDictionary {
public static final String tinyBlobTypeName = "TINYBLOB";
public static final String mediumBlobTypeName = "MEDIUMBLOB";
public static final String longBlobTypeName = "LONGBLOB";
public static final String tinyTextTypeName = "TINYTEXT";
public static final String mediumTextTypeName = "MEDIUMTEXT";
public static final String longTextTypeName = "LONGTEXT";
public MariaDBDictionary() {
platform = "MariaDB";
@ -120,15 +123,15 @@ public class MariaDBDictionary extends DBDictionary {
reservedWordSet.addAll(Arrays.asList(new String[]{
"AUTO_INCREMENT", "BINARY", "BLOB", "CHANGE", "ENUM", "INFILE",
"INT1", "INT2", "INT4", "FLOAT1", "FLOAT2", "FLOAT4", "LOAD",
"MEDIUMINT", "OUTFILE", "REPLACE", "STARTING", "TEXT", "UNSIGNED",
"ZEROFILL", "INDEX",
"MEDIUMINT", "OUTFILE", "REPLACE", "STARTING", "TEXT", "UNSIGNED",
"ZEROFILL", "INDEX",
}));
// reservedWordSet subset that CANNOT be used as valid column names
// (i.e., without surrounding them with double-quotes)
invalidColumnWordSet.addAll(Arrays.asList(new String[]{
"ADD", "ALL", "ALTER", "AND", "AS", "ASC", "BETWEEN", "BINARY",
"BLOB", "BOTH", "BY", "CASCADE", "CASE", "CHANGE", "CHAR",
"BLOB", "BOTH", "BY", "CASCADE", "CASE", "CHANGE", "CHAR",
"CHARACTER", "CHECK", "COLLATE", "COLUMN", "CONSTRAINT", "CONTINUE",
"CONVERT", "CREATE", "CROSS", "CURRENT_DATE", "CURRENT_TIME",
"CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "DEC", "DECIMAL",
@ -145,7 +148,7 @@ public class MariaDBDictionary extends DBDictionary {
"STARTING", "TABLE", "THEN", "TO", "TRAILING", "TRUE", "UNION",
"UNIQUE", "UNSIGNED", "UPDATE", "USAGE", "USING", "VALUES",
"VARCHAR", "VARYING", "WHEN", "WHERE", "WITH", "WRITE", "ZEROFILL",
"INDEX",
"INDEX",
}));
requiresSearchStringEscapeForLike = true;
@ -157,7 +160,7 @@ public class MariaDBDictionary extends DBDictionary {
setLeadingDelimiter(DELIMITER_BACK_TICK);
setTrailingDelimiter(DELIMITER_BACK_TICK);
fixedSizeTypeNameSet.remove("NUMERIC");
}
@ -180,10 +183,11 @@ public class MariaDBDictionary extends DBDictionary {
if (log.isWarnEnabled())
log.warn(e.toString(), e);
}
supportsXMLColumn = true;
}
@Override
protected void setDelimitedCase(DatabaseMetaData metaData) {
// Determination of case sensitivity is not accurate; MariaDB JIRA CONJ-55
delimitedCase = SCHEMA_CASE_PRESERVE;
@ -194,7 +198,7 @@ public class MariaDBDictionary extends DBDictionary {
conn = super.decorate(conn);
return conn;
}
private static int[] getMajorMinorVersions(String versionStr)
throws IllegalArgumentException {
int beginIndex = 0;
@ -263,7 +267,7 @@ public class MariaDBDictionary extends DBDictionary {
new String[]{ "ALTER TABLE "
+ getFullName(fk.getTable(), false)
+ " DROP FOREIGN KEY " + toDBName(fkName) };
return retVal;
return retVal;
}
return new String[]{ "ALTER TABLE "
+ getFullName(fk.getTable(), false)
@ -288,7 +292,7 @@ public class MariaDBDictionary extends DBDictionary {
System.arraycopy(sql, 0, ret, cols.length, sql.length);
return ret;
}
@Override
public String[] getDeleteTableContentsSQL(Table[] tables,Connection conn) {
// mysql >= 4 supports more-optimal delete syntax
@ -343,10 +347,10 @@ public class MariaDBDictionary extends DBDictionary {
return Types.LONGVARCHAR;
return super.getPreferredType(type);
}
/**
* Append XML comparison.
*
*
* @param buf the SQL buffer to write the comparison
* @param op the comparison operation to perform
* @param lhs the left hand side of the comparison
@ -368,10 +372,10 @@ public class MariaDBDictionary extends DBDictionary {
else
rhs.appendTo(buf);
}
/**
* Append XML column value so that it can be used in comparisons.
*
*
* @param buf the SQL buffer to write the value
* @param val the value to be written
*/
@ -382,7 +386,7 @@ public class MariaDBDictionary extends DBDictionary {
val.appendTo(buf);
buf.append("')");
}
@Override
public int getBatchFetchSize(int batchFetchSize) {
return Integer.MIN_VALUE;
@ -401,10 +405,10 @@ public class MariaDBDictionary extends DBDictionary {
select += " " + hint;
return select;
}
@Override
protected Collection<String> getSelectTableAliases(Select sel) {
Set<String> result = new HashSet<String>();
Set<String> result = new HashSet<>();
List<String> selects = sel.getIdentifierAliases();
for (String s : selects) {
String tableAlias = s.substring(0, s.indexOf('.'));
@ -412,12 +416,12 @@ public class MariaDBDictionary extends DBDictionary {
}
return result;
}
@Override
protected int matchErrorState(Map<Integer,Set<String>> errorStates, SQLException ex) {
int state = super.matchErrorState(errorStates, ex);
if (state == StoreException.GENERAL &&
if (state == StoreException.GENERAL &&
ex.getNextException() != null &&
"JZ0002".equalsIgnoreCase(ex.getNextException().getSQLState())) {
if (conf != null && conf.getLockTimeout() != -1) {
@ -449,7 +453,7 @@ public class MariaDBDictionary extends DBDictionary {
*/
@Override
public String getTypeName(Column col) {
// handle blobs differently, if the DBItentifierType is NULL (e.g. no column definition is set).
// handle blobs differently, if the DBItentifierType is NULL (e.g. no column definition is set).
if (col.getType() == Types.BLOB && col.getTypeIdentifier().getType() == DBIdentifierType.NULL) {
if (col.getSize() <= 0) // unknown size
return blobTypeName; // return old default of 64KB
@ -461,6 +465,17 @@ public class MariaDBDictionary extends DBDictionary {
return mediumBlobTypeName;
else
return longBlobTypeName;
} else if (col.getType() == Types.CLOB && col.getTypeIdentifier().getType() == DBIdentifierType.NULL) {
if (col.getSize() <= 0) // unknown size
return clobTypeName; // return old default of 64KB
else if (col.getSize() <= 255)
return tinyTextTypeName;
else if (col.getSize() <= 65535)
return clobTypeName; // old default of 64KB
else if (col.getSize() <= 16777215)
return mediumTextTypeName;
else
return longTextTypeName;
} else {
return super.getTypeName(col);
}

View File

@ -14,7 +14,7 @@
* "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.
* under the License.
*/
package org.apache.openjpa.jdbc.sql;
@ -30,7 +30,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.identifier.DBIdentifier.DBIdentifierType;
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
@ -41,6 +40,7 @@ import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Index;
import org.apache.openjpa.jdbc.schema.PrimaryKey;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.util.StoreException;
/**
@ -52,7 +52,7 @@ public class MySQLDictionary
public static final String SELECT_HINT = "openjpa.hint.MySQLSelectHint";
public static final String DELIMITER_BACK_TICK = "`";
/**
* The MySQL table type to use when creating tables; defaults to innodb.
*/
@ -70,8 +70,8 @@ public class MySQLDictionary
public boolean driverDeserializesBlobs = false;
/**
* Whether to inline multi-table bulk-delete operations into MySQL's
* combined <code>DELETE FROM foo, bar, baz</code> syntax.
* Whether to inline multi-table bulk-delete operations into MySQL's
* combined <code>DELETE FROM foo, bar, baz</code> syntax.
* Defaults to false, since this may fail in the presence of InnoDB tables
* with foreign keys.
* @see http://dev.mysql.com/doc/refman/5.0/en/delete.html
@ -81,6 +81,9 @@ public class MySQLDictionary
public static final String tinyBlobTypeName = "TINYBLOB";
public static final String mediumBlobTypeName = "MEDIUMBLOB";
public static final String longBlobTypeName = "LONGBLOB";
public static final String tinyTextTypeName = "TINYTEXT";
public static final String mediumTextTypeName = "MEDIUMTEXT";
public static final String longTextTypeName = "LONGTEXT";
public MySQLDictionary() {
platform = "MySQL";
@ -121,15 +124,15 @@ public class MySQLDictionary
reservedWordSet.addAll(Arrays.asList(new String[]{
"AUTO_INCREMENT", "BINARY", "BLOB", "CHANGE", "ENUM", "INFILE",
"INT1", "INT2", "INT4", "FLOAT1", "FLOAT2", "FLOAT4", "LOAD",
"MEDIUMINT", "OUTFILE", "REPLACE", "STARTING", "TEXT", "UNSIGNED",
"ZEROFILL", "INDEX",
"MEDIUMINT", "OUTFILE", "REPLACE", "STARTING", "TEXT", "UNSIGNED",
"ZEROFILL", "INDEX",
}));
// reservedWordSet subset that CANNOT be used as valid column names
// (i.e., without surrounding them with double-quotes)
invalidColumnWordSet.addAll(Arrays.asList(new String[]{
"ADD", "ALL", "ALTER", "AND", "AS", "ASC", "BETWEEN", "BINARY",
"BLOB", "BOTH", "BY", "CASCADE", "CASE", "CHANGE", "CHAR",
"BLOB", "BOTH", "BY", "CASCADE", "CASE", "CHANGE", "CHAR",
"CHARACTER", "CHECK", "COLLATE", "COLUMN", "CONSTRAINT", "CONTINUE",
"CONVERT", "CREATE", "CROSS", "CURRENT_DATE", "CURRENT_TIME",
"CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "DEC", "DECIMAL",
@ -146,7 +149,7 @@ public class MySQLDictionary
"STARTING", "TABLE", "THEN", "TO", "TRAILING", "TRUE", "UNION",
"UNIQUE", "UNSIGNED", "UPDATE", "USAGE", "USING", "VALUES",
"VARCHAR", "VARYING", "WHEN", "WHERE", "WITH", "WRITE", "ZEROFILL",
"INDEX",
"INDEX",
}));
requiresSearchStringEscapeForLike = true;
@ -158,7 +161,7 @@ public class MySQLDictionary
setLeadingDelimiter(DELIMITER_BACK_TICK);
setTrailingDelimiter(DELIMITER_BACK_TICK);
fixedSizeTypeNameSet.remove("NUMERIC");
}
@ -206,7 +209,7 @@ public class MySQLDictionary
conn.setReadOnly(true);
return conn;
}
private static int[] getMajorMinorVersions(String versionStr)
throws IllegalArgumentException {
int beginIndex = 0;
@ -275,7 +278,7 @@ public class MySQLDictionary
new String[]{ "ALTER TABLE "
+ getFullName(fk.getTable(), false)
+ " DROP FOREIGN KEY " + toDBName(fkName) };
return retVal;
return retVal;
}
return new String[]{ "ALTER TABLE "
+ getFullName(fk.getTable(), false)
@ -300,7 +303,7 @@ public class MySQLDictionary
System.arraycopy(sql, 0, ret, cols.length, sql.length);
return ret;
}
@Override
public String[] getDeleteTableContentsSQL(Table[] tables,Connection conn) {
// mysql >= 4 supports more-optimal delete syntax
@ -356,10 +359,10 @@ public class MySQLDictionary
return Types.LONGVARCHAR;
return super.getPreferredType(type);
}
/**
* Append XML comparison.
*
*
* @param buf the SQL buffer to write the comparison
* @param op the comparison operation to perform
* @param lhs the left hand side of the comparison
@ -381,10 +384,10 @@ public class MySQLDictionary
else
rhs.appendTo(buf);
}
/**
* Append XML column value so that it can be used in comparisons.
*
*
* @param buf the SQL buffer to write the value
* @param val the value to be written
*/
@ -395,7 +398,7 @@ public class MySQLDictionary
val.appendTo(buf);
buf.append("')");
}
@Override
public int getBatchFetchSize(int batchFetchSize) {
return Integer.MIN_VALUE;
@ -414,10 +417,10 @@ public class MySQLDictionary
select += " " + hint;
return select;
}
@Override
protected Collection<String> getSelectTableAliases(Select sel) {
Set<String> result = new HashSet<String>();
Set<String> result = new HashSet<>();
List<String> selects = sel.getIdentifierAliases();
for (String s : selects) {
String tableAlias = s.substring(0, s.indexOf('.'));
@ -425,7 +428,7 @@ public class MySQLDictionary
}
return result;
}
@Override
protected int matchErrorState(Map<Integer,Set<String>> errorStates, SQLException ex) {
int state = super.matchErrorState(errorStates, ex);
@ -462,7 +465,7 @@ public class MySQLDictionary
*/
@Override
public String getTypeName(Column col) {
// handle blobs differently, if the DBItentifierType is NULL (e.g. no column definition is set).
// handle blobs differently, if the DBItentifierType is NULL (e.g. no column definition is set).
if (col.getType() == Types.BLOB && col.getTypeIdentifier().getType() == DBIdentifierType.NULL) {
if (col.getSize() <= 0) // unknown size
return blobTypeName; // return old default of 64KB
@ -474,6 +477,17 @@ public class MySQLDictionary
return mediumBlobTypeName;
else
return longBlobTypeName;
} else if (col.getType() == Types.CLOB && col.getTypeIdentifier().getType() == DBIdentifierType.NULL) {
if (col.getSize() <= 0) // unknown size
return clobTypeName; // return old default of 64KB
else if (col.getSize() <= 255)
return tinyTextTypeName;
else if (col.getSize() <= 65535)
return clobTypeName; // old default of 64KB
else if (col.getSize() <= 16777215)
return mediumTextTypeName;
else
return longTextTypeName;
} else {
return super.getTypeName(col);
}

View File

@ -0,0 +1,85 @@
/*
* 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.persistence.blob.mysql;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
@Entity
public class ClobColumnEntity {
@Id
private int id;
@Lob
@Column(length = 20)
protected String smallLob;
@Lob
@Column(length = 66000)
protected String medLob;
@Lob
@Column(length = 16777216)
protected String longLob;
@Lob
protected String defaultLob;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getSmallLob() {
return smallLob;
}
public void setSmallLob(String smallLob) {
this.smallLob = smallLob;
}
public String getMedLob() {
return medLob;
}
public void setMedLob(String medLob) {
this.medLob = medLob;
}
public String getLongLob() {
return longLob;
}
public void setLongLob(String longLob) {
this.longLob = longLob;
}
public String getDefaultLob() {
return defaultLob;
}
public void setDefaultLob(String defaultLob) {
this.defaultLob = defaultLob;
}
}

View File

@ -0,0 +1,95 @@
/*
* 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.persistence.blob.mysql;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.identifier.DBIdentifier.DBIdentifierType;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.jdbc.sql.MariaDBDictionary;
import org.apache.openjpa.jdbc.sql.MySQLDictionary;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
/**
* Testcase for MySQL Blob types. OPENJPA-740 introduced intelligent column type for BLOBs, OPENJPA-1870 refined it a
* bit.
*/
public class TestClobColumnType extends SingleEMFTestCase {
private static boolean _firstRun=true;
private boolean _runTest = false; // only test with MySQL
@Override
public void setUp() throws Exception {
// create EMF solely to obtain a DBDictionary.
// need to do this without ClobColumnEntity.class since it contains a column definition which might
// not work with all databases.
super.setUp((Object) null);
if (!(getDBDictionary() instanceof MySQLDictionary || getDBDictionary() instanceof MariaDBDictionary)) {
// normal teardown will take care of the EMF.
return;
}
// remove the EMF
tearDown();
_runTest = true;
super.setUp(ClobColumnEntity.class, DROP_TABLES, "openjpa.jdbc.SchemaFactory", "native");
if(_firstRun) {
emf.createEntityManager().close(); // trigger table creation.
_firstRun = false;
}
}
private Column getCol(String name) {
ClassMapping mapping = getMapping(ClobColumnEntity.class);
Table t = mapping.getTable();
Column col = t.getColumn(DBIdentifier.newIdentifier(name, DBIdentifierType.COLUMN, true));
assertNotNull(col);
return col;
}
public void testSmallLob() {
if (_runTest) {
assertEquals(MySQLDictionary.tinyTextTypeName, getCol("smallLob").getTypeIdentifier().getName());
}
}
public void testMedLob() {
if (_runTest) {
assertEquals(MySQLDictionary.mediumTextTypeName, getCol("medLob").getTypeIdentifier().getName());
}
}
public void testLongBlob() {
if (_runTest) {
assertEquals(MySQLDictionary.longTextTypeName, getCol("longLob").getTypeIdentifier().getName());
}
}
public void testDefaultLob() {
if (_runTest) {
assertEquals(getDBDictionary().blobTypeName, getCol("defaultLob").getTypeIdentifier().getName());
}
}
}