blob: ca5a286131f60050e48f417e19cc3fa216808216 [file] [log] [blame]
/*
* 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
// 11/19/2012-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support)
// 11/22/2012-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support (index metadata support)
// 12/07/2012-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support)
package org.eclipse.persistence.internal.helper;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.internal.core.helper.CoreTable;
import org.eclipse.persistence.internal.databaseaccess.*;
import org.eclipse.persistence.internal.expressions.ExpressionSQLPrinter;
import org.eclipse.persistence.tools.schemaframework.ForeignKeyConstraint;
import org.eclipse.persistence.tools.schemaframework.IndexDefinition;
/**
* INTERNAL:
* <p> <b>Purpose</b>:
* Define a fully qualified table name.<p>
* <b>Responsibilities</b>: <ul>
* <li> Allow specification of a qualifier to the table, i.e. creator or database.
* </ul>
*@see DatabaseField
*/
public class DatabaseTable implements CoreTable, Cloneable, Serializable {
protected String name;
protected String tableQualifier;
protected String qualifiedName;
/** JPA 2.1 Foreign key specification data */
protected Map<String, ForeignKeyConstraint> foreignKeyConstraints;
/**
* Contains the user specified unique constraints. JPA 2.0 introduced
* the name element, therefore, if specified we will use that name
* to create the constraint. Constraints with no name will be added to the
* map under the null key and generated with a default name.
* Therefore, when a name is given the list size should only ever be
* 1. We will validate. The null key could have multiples however they will
* have their names defaulted (as we did before).
*/
protected Map<String, List<List<String>>> uniqueConstraints;
/**
* Store the set of indexes defined through meta-data for the table.
*/
protected List<IndexDefinition> indexes;
protected boolean useDelimiters = false;
protected String creationSuffix;
/**
* Initialize the newly allocated instance of this class.
* By default their is no qualifier.
*/
public DatabaseTable() {
name = "";
tableQualifier = "";
}
public DatabaseTable(String possiblyQualifiedName) {
this(possiblyQualifiedName, null, null);
}
public DatabaseTable(String possiblyQualifiedName, String startDelimiter, String endDelimiter) {
setPossiblyQualifiedName(possiblyQualifiedName, startDelimiter, endDelimiter);
}
public DatabaseTable(String tableName, String qualifier) {
this(tableName, qualifier, false, null, null);
}
public DatabaseTable(String tableName, String qualifier, boolean useDelimiters, String startDelimiter, String endDelimiter) {
setName(tableName, startDelimiter, endDelimiter);
this.tableQualifier = qualifier;
this.useDelimiters = useDelimiters;
}
public void addForeignKeyConstraint(ForeignKeyConstraint foreignKeyConstraint) {
if (foreignKeyConstraints == null) {
foreignKeyConstraints = new HashMap<>();
}
foreignKeyConstraints.put(foreignKeyConstraint.getName(), foreignKeyConstraint);
}
/**
* Add an index definition to this table.
*/
public void addIndex(IndexDefinition index) {
getIndexes().add(index);
}
/**
* Add the unique constraint for the columns names. Used for DDL generation.
* For now we just add all the unique constraints as we would have before
* when we didn't have a name.
*/
public void addUniqueConstraints(String name, List<String> columnNames) {
if (getUniqueConstraints().containsKey(name)) {
getUniqueConstraints().get(name).add(columnNames);
} else {
List<List<String>> value = new ArrayList<>();
value.add(columnNames);
getUniqueConstraints().put(name, value);
}
}
/**
* Return a shallow copy of the receiver.
*/
@Override
public DatabaseTable clone() {
try {
return (DatabaseTable)super.clone();
} catch (CloneNotSupportedException exception) {
throw new InternalError(exception.getMessage());
}
}
/**
* Two tables are equal if their names and tables are equal,
* or their names are equal and one does not have a qualifier assigned.
* This allows an unqualified table to equal the same fully qualified one.
*/
@Override
public boolean equals(Object object) {
if (object instanceof DatabaseTable) {
return equals((DatabaseTable)object);
}
return false;
}
/**
* Two tables are equal if their names and tables are equal,
* or their names are equal and one does not have a qualifier assigned.
* This allows an unqualified table to equal the same fully qualified one.
*/
public boolean equals(DatabaseTable table) {
if (this == table) {
return true;
}
if (table == null) {
return false;
}
if (DatabasePlatform.shouldIgnoreCaseOnFieldComparisons) {
if (this.name.equalsIgnoreCase(table.name)) {
if ((this.tableQualifier.length() == 0) || (table.tableQualifier.length() == 0) || (this.tableQualifier.equalsIgnoreCase(table.tableQualifier))) {
return true;
}
}
} else {
if (this.name.equals(table.name)) {
if ((this.tableQualifier.length() == 0) || (table.tableQualifier.length() == 0) || (this.tableQualifier.equals(table.tableQualifier))) {
return true;
}
}
}
return false;
}
/**
* returns the suffix applied to the CREATE table statement on this field for DDL generation.
*/
public String getCreationSuffix() {
return creationSuffix;
}
public ForeignKeyConstraint getForeignKeyConstraint(String name) {
return foreignKeyConstraints.get(name);
}
public Map<String, ForeignKeyConstraint> getForeignKeyConstraints() {
return foreignKeyConstraints;
}
/**
* Return a list of index definitions.
* Used for DDL generation.
*/
public List<IndexDefinition> getIndexes() {
if (this.indexes == null) {
this.indexes = new ArrayList<>();
}
return this.indexes;
}
/**
* Get method for table name.
*/
public String getName() {
return name;
}
/**
* Get method for table name.
*/
public String getNameDelimited(DatasourcePlatform platform) {
if (useDelimiters){
return platform.getStartDelimiter() + name + platform.getEndDelimiter();
}
return name;
}
public String getQualifiedName() {
if (qualifiedName == null) {
if (tableQualifier.equals("")) {
qualifiedName = getName();
} else {
qualifiedName = getTableQualifier() + "." + getName();
}
}
return qualifiedName;
}
public String getQualifiedNameDelimited(DatasourcePlatform platform) {
if (tableQualifier.equals("")) {
if (useDelimiters){
return platform.getStartDelimiter() + getName() + platform.getEndDelimiter();
} else {
return getName();
}
} else {
if (useDelimiters){
return platform.getStartDelimiter() + getTableQualifier() + platform.getEndDelimiter() + "."
+ platform.getStartDelimiter() + getName() + platform.getEndDelimiter();
} else {
return getTableQualifier() + "." + getName();
}
}
}
/**
* Print the table's SQL from clause.
*/
public void printSQL(ExpressionSQLPrinter printer) throws IOException {
printer.getWriter().write(getQualifiedNameDelimited(printer.getPlatform()));
}
public String getTableQualifierDelimited(DatasourcePlatform platform) {
if (useDelimiters && tableQualifier != null && !tableQualifier.equals("")){
return platform.getStartDelimiter() + tableQualifier + platform.getEndDelimiter();
}
return tableQualifier;
}
public String getTableQualifier() {
return tableQualifier;
}
public boolean hasUniqueConstraints() {
return (this.uniqueConstraints != null) && (!this.uniqueConstraints.isEmpty());
}
public boolean hasForeignKeyConstraints() {
return foreignKeyConstraints != null;
}
/**
* Return the hashcode of the name, because it is fairly unique.
*/
@Override
public int hashCode() {
return getName().hashCode();
}
public boolean hasIndexes() {
return (this.indexes != null) && (!this.indexes.isEmpty());
}
/**
* Return a list of the unique constraints for this table.
* Used for DDL generation.
*/
public Map<String, List<List<String>>> getUniqueConstraints() {
if (this.uniqueConstraints == null) {
this.uniqueConstraints = new HashMap<>();
}
return this.uniqueConstraints;
}
/**
* Determine whether the receiver has any identification information.
* Return true if the name or qualifier of the receiver are nonempty.
*/
public boolean hasName() {
if ((getName().length() == 0) && (getTableQualifier().length() == 0)) {
return false;
}
return true;
}
/**
* INTERNAL:
* Is this decorated / has an AS OF (some past time) clause.
* <b>Example:</b>
* SELECT ... FROM EMPLOYEE AS OF TIMESTAMP (exp) t0 ...
*/
public boolean isDecorated() {
return false;
}
protected void resetQualifiedName() {
this.qualifiedName = null;
}
public void setCreationSuffix(String creationSuffix) {
this.creationSuffix = creationSuffix;
}
/**
* Set the table name.
* Used when aliasing table names.
*/
public void setName(String name) {
setName(name, null, null);
}
/**
* Set the table name.
* Used when aliasing table names.
*
* If the name contains database delimiters, they will be stripped and a flag will be set to have them
* added when the DatabaseTable is written to SQL
*
*/
public void setName(String name, String startDelimiter, String endDelimiter) {
if (name != null && (startDelimiter != null) && (endDelimiter != null) && !startDelimiter.equals("")&& !endDelimiter.equals("") && name.startsWith(startDelimiter) && name.endsWith(endDelimiter)){
this.name = name.substring(startDelimiter.length(), name.length() - endDelimiter.length());
useDelimiters = true;
} else {
this.name = name ;
}
resetQualifiedName();
}
/**
* Used to map the project xml. Any time a string name is read from the
* project xml, we must check if it is fully qualified and split the
* actual name from the qualifier.
*
*/
public void setPossiblyQualifiedName(String possiblyQualifiedName) {
setPossiblyQualifiedName(possiblyQualifiedName, null, null);
}
public void setPossiblyQualifiedName(String possiblyQualifiedName, String startDelimiter, String endDelimiter) {
resetQualifiedName();
int index = possiblyQualifiedName.lastIndexOf('.');
if (index == -1) {
setName(possiblyQualifiedName, startDelimiter, endDelimiter);
this.tableQualifier = "";
} else {
setName(possiblyQualifiedName.substring(index + 1, possiblyQualifiedName.length()), startDelimiter, endDelimiter);
setTableQualifier(possiblyQualifiedName.substring(0, index), startDelimiter, endDelimiter);
if((startDelimiter != null) && possiblyQualifiedName.startsWith(startDelimiter) && (endDelimiter != null) && possiblyQualifiedName.endsWith(endDelimiter)) {
// It's 'Qualifier.Name' - it should be treated as a single string.
// Would that be 'Qualifier'.'Name' both setName and setTableQualifier methods would have set useDelimeters to true.
if(!this.useDelimiters) {
setName(possiblyQualifiedName);
this.tableQualifier = "";
}
}
}
}
public void setTableQualifier(String qualifier) {
setTableQualifier(qualifier, null, null);
}
public void setTableQualifier(String qualifier, String startDelimiter, String endDelimiter) {
if ((startDelimiter != null) && (endDelimiter != null) && !startDelimiter.equals("")&& !endDelimiter.equals("") && qualifier.startsWith(startDelimiter) && qualifier.endsWith(endDelimiter)){
this.tableQualifier = qualifier.substring(startDelimiter.length(), qualifier.length() - endDelimiter.length());
useDelimiters = true;
} else {
this.tableQualifier = qualifier;
}
resetQualifiedName();
}
@Override
public String toString() {
return "DatabaseTable(" + getQualifiedName() + ")";
}
public void setUseDelimiters(boolean useDelimiters) {
this.useDelimiters = useDelimiters;
}
public boolean shouldUseDelimiters() {
return useDelimiters;
}
}