org.eclipse.persistence.internal.helper.DatabaseField Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
// tware - added handling of database delimiters
// 03/24/2011-2.3 Guy Pelletier
// - 337323: Multi-tenant with shared schema support (part 1)
// 05/30/2012-2.4 Guy Pelletier
// - 354678: Temp classloader is still being used during metadata processing
// 02/11/2013-2.5 Guy Pelletier
// - 365931: @JoinColumn(name="FK_DEPT",insertable = false, updatable = true) causes INSERT statement to include this data value that it is associated with
package org.eclipse.persistence.internal.helper;
//javase imports
import java.io.Serializable;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.core.helper.CoreField;
import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform;
import org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import static java.lang.Integer.MIN_VALUE;
/**
* INTERNAL:
* Purpose:
* Define a fully qualified field name.
* Responsibilities:
* - Know its name and its table.
*
* @see DatabaseTable
*/
public class DatabaseField implements Cloneable, Serializable, CoreField {
/** Variables used for generating DDL **/
protected int scale;
protected int length;
protected int precision;
protected boolean isUnique;
protected boolean isNullable;
protected boolean isUpdatable;
protected boolean isInsertable;
protected boolean isCreatable;
protected boolean isPrimaryKey;
protected String columnDefinition;
/** Column name of the field. */
protected String name;
/** PERF: Cache fully qualified table.field-name. */
protected String qualifiedName;
/** Fields table (encapsulates name + creator). */
protected DatabaseTable table;
/**
* Respective Java type desired for the field's value, used to optimize performance and for binding.
* PERF: Allow direct variable access from getObject.
*/
public transient Class> type;
public String typeName; // shadow variable - string name of above Class type variable
/**
* Respective JDBC type of the field's value.
* This overrides the class type, which the JDBC type is normally computed from.
* PERF: Allow direct variable access from getObject.
*/
public int sqlType;
/**
* Store normal index of field in result set to optimize performance.
* PERF: Allow direct variable access from getIndicatingNoEntry.
*/
public int index;
protected boolean useDelimiters = false;
/**
* If this is set, it will be used in determining equality (unless delimiters are used) and the hashcode.
* @see #getNameForComparisons()
*/
protected String nameForComparisons;
/**
* setting to true will cause getNameForComparisons to lazy initialize nameForComparisons using
* the value from getName().toUpperCase().
*/
protected boolean useUpperCaseForComparisons = false;
/**
* used to represent the value when it has not being defined
*/
public static final int NULL_SQL_TYPE = MIN_VALUE;
/**
* Returns true if this field was translated.
*/
protected boolean isTranslated = false;
/**
* Indicates whether the field should be kept in the record after the object is created.
* Used by ObjectLevelReadQuery ResultSetAccessOptimization.
*/
public boolean keepInRow;
public DatabaseField() {
this("", new DatabaseTable());
}
public DatabaseField(String qualifiedName) {
this(qualifiedName, null, null);
}
public DatabaseField(String qualifiedName, String startDelimiter, String endDelimiter) {
this.index = -1;
this.sqlType = NULL_SQL_TYPE;
int index = qualifiedName.lastIndexOf('.');
if (index == -1) {
setName(qualifiedName, startDelimiter, endDelimiter);
this.table = new DatabaseTable();
} else {
setName(qualifiedName.substring(index + 1, qualifiedName.length()), startDelimiter, endDelimiter);
this.table = new DatabaseTable(qualifiedName.substring(0, index), startDelimiter, endDelimiter);
}
initDDLFields();
}
public DatabaseField(String fieldName, String tableName) {
this(fieldName, new DatabaseTable(tableName));
}
public DatabaseField(String fieldName, DatabaseTable databaseTable) {
this(fieldName, databaseTable, null, null);
}
public DatabaseField(String fieldName, DatabaseTable databaseTable, String startDelimiter, String endDelimiter) {
this.index = -1;
this.sqlType = NULL_SQL_TYPE;
setName(fieldName, startDelimiter, endDelimiter);
this.table = databaseTable;
initDDLFields();
}
/**
* Inits the DDL generation fields with our defaults. Note: we used to
* initialize the length to the JPA default of 255 but since this default
* value should only apply for string fields we set it to 0 to indicate
* that it was not specified and rely on the default (255) to come from
* individual platforms.
*/
public void initDDLFields() {
scale = 0;
length = 0;
precision = 0;
isUnique = false;
isNullable = true;
isUpdatable = true;
isInsertable = true;
isCreatable = true;
isPrimaryKey = false;
columnDefinition = "";
}
/**
* The table is not cloned because it is treated as an automatic value.
*/
@Override
public DatabaseField clone() {
try {
return (DatabaseField)super.clone();
} catch (CloneNotSupportedException exception) {
throw new InternalError(exception.getMessage());
}
}
/*
* INTERNAL:
* Convert all the class-name-based settings in this mapping to actual
* class-based settings. This method is implemented by subclasses as
* necessary.
* @param classLoader
*/
public void convertClassNamesToClasses(ClassLoader classLoader) {
if (type == null && typeName != null) {
type = PrivilegedAccessHelper.callDoPrivilegedWithException(
() -> PrivilegedAccessHelper.getClassForName(typeName, true, classLoader),
(ex) -> ValidationException.classNotFoundWhileConvertingClassNames(typeName, ex)
);
}
}
/**
* Determine whether the receiver is equal to a DatabaseField.
* Return true if the receiver and field have the same name and table.
* Also return true if the table of the receiver or field are unspecified,
* ie. have no name.
*/
@Override
public boolean equals(Object object) {
if (!(object instanceof DatabaseField)) {
return false;
}
return equals((DatabaseField)object);
}
/**
* Determine whether the receiver is equal to a DatabaseField.
* Return true if the receiver and field have the same name and table.
* Also return true if the table of the receiver or field are unspecified,
* ie. have no name.
*/
public boolean equals(DatabaseField field) {
if (this == field) {
return true;
}
if (field != null) {
// PERF: Optimize common cases first.
// PERF: Use direct variable access.
if (getQualifiedName().equals(field.getQualifiedName())) {
return true;
}
//preserve old behavior if static shouldIgnoreCaseOnFieldComparisons is set
if (DatabasePlatform.shouldIgnoreCaseOnFieldComparisons()) {
if (this.name.equalsIgnoreCase(field.name)) {
//getTableName will cause NPE if there isn't a table. use hasTableName instead
if ((!hasTableName()) || (!field.hasTableName())) {
return true;
}
return (this.table.equals(field.table));
}
} else {
String ourNameToCompare;
String fieldNameToCompare;
if (field.shouldUseDelimiters() || shouldUseDelimiters()) {
ourNameToCompare = this.name;
fieldNameToCompare = field.name;
} else {
ourNameToCompare = getNameForComparisons();
fieldNameToCompare = field.getNameForComparisons();
}
if (this.name.equals(field.name) || ourNameToCompare.equals(fieldNameToCompare)) {
//getTableName will cause NPE if there isn't a table. use hasTableName instead
if ((!hasTableName()) || (!field.hasTableName())) {
return true;
}
return (this.table.equals(field.table));
}
}
}
return false;
}
/**
* Get the SQL fragment that is used when generating the DDL for the column.
*/
public String getColumnDefinition() {
return this.columnDefinition;
}
/**
* Return the expected index that this field will occur in the result set
* row. This is used to optimize performance of database row field lookups.
*/
public int getIndex() {
return index;
}
/**
* Used to specify the column length when generating DDL.
*/
public int getLength() {
return this.length;
}
/**
* Return the unqualified name of the field.
*/
@Override
public String getName() {
return name;
}
/**
* Returns this fields name with database delimiters if useDelimiters is true.
* This method should be called any time the field name is requested for writing SQL.
*/
public String getNameDelimited(DatasourcePlatform platform) {
if (this.useDelimiters){
return platform.getStartDelimiter() + this.name + platform.getEndDelimiter();
}
return this.name;
}
/**
* Returns the precision for a decimal column when generating DDL.
*/
public int getPrecision() {
return this.precision;
}
public String getQualifiedName(){
if (this.qualifiedName == null) {
if (hasTableName()) {
this.qualifiedName = this.table.getQualifiedName() + "." + getName();
} else {
this.qualifiedName = getName();
}
}
return this.qualifiedName;
}
/**
* Return the qualified name of the field.
* PERF: Cache the qualified name.
*/
public String getQualifiedNameDelimited(DatasourcePlatform platform) {
if (hasTableName()) {
return this.table.getQualifiedNameDelimited(platform) + "." + getNameDelimited(platform);
} else {
return getNameDelimited(platform);
}
}
/**
* Returns the scale for a decimal column when generating DDL.
*/
public int getScale() {
return this.scale;
}
public DatabaseTable getTable() {
return table;
}
public String getTableName() {
return getTable().getName();
}
public void setTableName(String tableName) {
setTable(new DatabaseTable(tableName));
}
@Override
public Class> getType() {
if ((this.type == null) && (this.typeName != null)) {
convertClassNamesToClasses(getClass().getClassLoader());
}
return this.type;
}
public String getTypeName() {
return typeName;
}
public void setTypeName(String typeName) {
this.typeName = typeName;
}
/**
* Return the JDBC type that corresponds to the field.
* The JDBC type is normally determined from the class type,
* but this allows it to be overridden for types that do not match directly to a Java type,
* such as MONEY or ARRAY, STRUCT, XMLTYPE, etc.
* This can be used for binding or stored procedure usage.
*/
public int getSqlType() {
return sqlType;
}
/**
* Return the hashcode of the name, because it is fairly unique.
*/
@Override
public int hashCode() {
return getNameForComparisons().hashCode();
}
public boolean hasTableName() {
if (this.table == null) {
return false;
}
if (this.table.getName() == null) {
return false;
}
return !(this.table.getName().equals(""));
}
/**
* PUBLIC:
* Return if this is an ObjectRelationalDatabaseField.
*/
public boolean isObjectRelationalDatabaseField(){
return false;
}
/**
* Used to specify whether the column should be included in SQL UPDATE
* statements.
*/
public boolean isInsertable() {
return this.isInsertable;
}
/**
* Used for generating DDL. Returns true if the database column is
* nullable.
*/
public boolean isNullable() {
return this.isNullable;
}
/**
* Used to specify whether the column should be included in the primary
* on the database table.
*/
public boolean isPrimaryKey() {
return this.isPrimaryKey;
}
/**
* Return true if this database field is a translation.
*/
public boolean isTranslated() {
return this.isTranslated;
}
/**
* Used for generating DDL. Returns true if the field is a unique key.
*/
public boolean isUnique() {
return this.isUnique;
}
/**
* Returns true is this database field should be read only.
*/
public boolean isReadOnly() {
return (! isUpdatable && ! isInsertable);
}
public boolean keepInRow() {
return keepInRow;
}
/**
* Returns whether the column should be included in SQL INSERT
* statements.
*/
public boolean isUpdatable() {
return this.isUpdatable;
}
/**
* Reset the field's name and table from the qualified name.
*/
public void resetQualifiedName(String qualifiedName) {
setIndex(-1);
int index = qualifiedName.lastIndexOf('.');
if (index == -1) {
setName(qualifiedName);
getTable().setName("");
getTable().setTableQualifier("");
} else {
setName(qualifiedName.substring(index + 1, qualifiedName.length()));
getTable().setPossiblyQualifiedName(qualifiedName.substring(0, index));
}
}
/**
* Set the SQL fragment that is used when generating the DDL for the column.
*/
public void setColumnDefinition(String columnDefinition) {
this.columnDefinition = columnDefinition;
}
/**
* Set the expected index that this field will occur in the result set row.
* This is used to optimize performance of database row field lookups.
*/
public void setIndex(int index) {
this.index = index;
}
/**
* Used to specify whether the column should be included in SQL UPDATE
* statements.
*/
public void setInsertable(boolean isInsertable) {
this.isInsertable = isInsertable;
}
public void setKeepInRow(boolean keepInRow) {
this.keepInRow = keepInRow;
}
/**
* Set the isTranslated flag.
*/
public void setIsTranslated(boolean isTranslated) {
this.isTranslated = isTranslated;
}
/**
* Used to specify the column length when generating DDL.
*/
public void setLength(int length) {
this.length = length;
}
/**
* Set the unqualified name of the field.
*/
@Override
public void setName(String name) {
setName(name, null, null);
}
/**
* Set the unqualified name of the field.
*
* If the name contains database delimiters, they will be stripped and a flag will be set to have them
* added when the DatabaseField is written to SQL
*/
public void setName(String name, DatasourcePlatform platform){
setName(name, platform.getStartDelimiter(), platform.getEndDelimiter());
}
/**
* Set the unqualified name of the field.
*
* If the name contains database delimiters, they will be stripped and a flag will be set to have them
* added when the DatabaseField is written to SQL
*/
public void setName(String name, String startDelimiter, String endDelimiter) {
if ((startDelimiter != null) && (endDelimiter != null) && !startDelimiter.equals("")&& !endDelimiter.equals("") && name.startsWith(startDelimiter) && name.endsWith(endDelimiter)){
this.name = name.substring(startDelimiter.length(), name.length() - endDelimiter.length());
this.useDelimiters = true;
} else {
this.name = name;
}
this.nameForComparisons = null;
this.qualifiedName = null;
}
/**
* Used for generating DDL. Set to true if the database column is
* nullable.
*/
public void setNullable(boolean isNullable) {
this.isNullable = isNullable;
}
/**
* Used to specify the precision for a decimal column when generating DDL.
*/
public void setPrecision(int precision) {
this.precision = precision;
}
/**
* Used to specify whether the column should be included in primary key
* on the database table.
*/
public void setPrimaryKey(boolean isPrimaryKey) {
this.isPrimaryKey = isPrimaryKey;
}
/**
* Used to specify the scale for a decimal column when generating DDL.
*/
public void setScale(int scale) {
this.scale = scale;
}
/**
* Set the JDBC type that corresponds to the field.
* The JDBC type is normally determined from the class type,
* but this allows it to be overridden for types that do not match directly
* to a Java type, such as MONEY or ARRAY, STRUCT, XMLTYPE, etc.
* This can be used for binding or stored procedure usage.
*/
public void setSqlType(int sqlType) {
this.sqlType = sqlType;
}
/**
* Set the table for the field.
*/
public void setTable(DatabaseTable table) {
this.table = table;
this.qualifiedName = null;
}
/**
* Set the Java class type that corresponds to the field.
* The JDBC type is determined from the class type,
* this is used to optimize performance, and for binding.
*/
@Override
public void setType(Class> type) {
this.type = type;
if (this.type != null && typeName == null) {
typeName = this.type.getName();
}
}
/**
* Used for generating DDL. Set to true if the field is a unique key.
*/
public void setUnique(boolean isUnique) {
this.isUnique = isUnique;
}
/**
* Used to specify whether the column should be included in SQL INSERT
* statements.
*/
public void setUpdatable(boolean isUpdatable) {
this.isUpdatable = isUpdatable;
}
@Override
public String toString() {
return this.getQualifiedName();
}
public void setUseDelimiters(boolean useDelimiters) {
this.useDelimiters = useDelimiters;
}
public boolean shouldUseDelimiters() {
return this.useDelimiters;
}
/**
* INTERNAL:
* Sets the useUpperCaseForComparisons flag which is used to force using the uppercase version of the field's
* name to determine field equality and its hashcode, but will still use the original name when writing/printing
* operations. If this isn't a change, it is ignored, otherwise it sets the nameForComparisons to null.
*/
public void useUpperCaseForComparisons(boolean useUpperCaseForComparisons){
if (this.useUpperCaseForComparisons != useUpperCaseForComparisons){
this.useUpperCaseForComparisons = useUpperCaseForComparisons;
this.setNameForComparisons(null);
}
}
public boolean getUseUpperCaseForComparisons(){
return this.useUpperCaseForComparisons;
}
/**
* INTERNAL:
* sets the string to be used for equality checking and determining the hashcode of this field.
* This will overwrite the useUpperCaseForEquality setting with the passed in string.
*/
public void setNameForComparisons(String name){
this.nameForComparisons = name;
}
public boolean isCreatable() {
return isCreatable;
}
public void setCreatable(boolean isCreatable) {
this.isCreatable = isCreatable;
}
/**
* INTERNAL:
* gets the string used for comparisons and in determining the hashcode.
*/
public String getNameForComparisons(){
if (this.nameForComparisons == null) {
if ((!this.useUpperCaseForComparisons) || (this.name == null)) {
this.nameForComparisons = this.name;
} else {
this.nameForComparisons = this.name.toUpperCase();
}
}
return this.nameForComparisons;
}
}