package com.tesora.dve.common.catalog;
/*
* #%L
* Tesora Inc.
* Database Virtualization Engine
* %%
* Copyright (C) 2011 - 2014 Tesora Inc.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import com.tesora.dve.db.DBNative;
import org.hibernate.annotations.ForeignKey;
import com.tesora.dve.common.PEConstants;
import com.tesora.dve.db.DBEmptyTextResultConsumer;
import com.tesora.dve.distribution.ContainerDistributionModel;
import com.tesora.dve.distribution.DistributionRange;
import com.tesora.dve.distribution.KeyTemplate;
import com.tesora.dve.distribution.KeyValue;
import com.tesora.dve.distribution.RangeDistributionModel;
import com.tesora.dve.distribution.RangeTableRelationship;
import com.tesora.dve.exceptions.PECodingException;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.groupmanager.GroupManager;
import com.tesora.dve.queryplan.ExecutionState;
import com.tesora.dve.queryplan.QueryStepDDLOperation;
import com.tesora.dve.resultset.ColumnMetadata;
import com.tesora.dve.resultset.ColumnSet;
import com.tesora.dve.resultset.ResultRow;
import com.tesora.dve.server.connectionmanager.SSConnection;
import com.tesora.dve.server.global.HostService;
import com.tesora.dve.server.messaging.SQLCommand;
import com.tesora.dve.singleton.Singletons;
import com.tesora.dve.variables.VariableStoreSource;
import com.tesora.dve.worker.WorkerGroup;
import com.tesora.dve.worker.WorkerGroup.Manager;
import com.tesora.dve.worker.WorkerGroup.WorkerGroupFactory;
/**
* Entity implementation class for Entity: UserTable
*
*/
@Entity
@Table(name = "user_table"
// would have been really nice to put this in, but then would have to worry about
// collation on name
// ,uniqueConstraints=@UniqueConstraint(columnNames = {"name","user_database_id"})
)
public class UserTable implements CatalogEntity, HasAutoIncrementTracker, NamedCatalogEntity, PersistentTable {
private static final String TEMP_TABLE_PREFIX = "temp";
@Id
@GeneratedValue
@Column(name = "table_id")
int id;
@Column(name = "name", nullable = false)
String name;
// Optional: used to store the db native CREATE TABLE stmt
@Column(name = "create_table_stmt", nullable = true)
@Lob
String createTableStmt;
@ForeignKey(name="fk_table_model")
@ManyToOne(optional = false)
@JoinColumn(name = "distribution_model_id")
DistributionModel distributionModel;
@ForeignKey(name="fk_table_sg")
@ManyToOne(optional = false)
@JoinColumn(name = "persistent_group_id")
PersistentGroup persistentGroup;
@ForeignKey(name="fk_table_db")
@ManyToOne(optional = false)
@JoinColumn(name = "user_database_id")
UserDatabase userDatabase;
// optional is true because unused in modes other than mt relaxed and adaptive
@ForeignKey(name="fk_table_shape")
@ManyToOne(optional=true)
@JoinColumn(name="shape_id")
Shape shape;
// have to use string for info schema
// state really only pertains to mt modes
@Column(name = "state")
@Enumerated(EnumType.STRING)
TableState state;
// contained keys
@OneToMany(mappedBy="userTable")
@OrderBy("position ASC")
List<Key> keys = new ArrayList<Key>();
@OneToMany(mappedBy="referencedTable")
Set<Key> referencing = new HashSet<Key>();
@OneToMany(cascade=CascadeType.ALL, mappedBy="userTable", fetch=FetchType.LAZY)
@OrderBy("orderInTable ASC")
List<UserColumn> userColumns = new ArrayList<UserColumn>();
@OneToOne(mappedBy="table", cascade=CascadeType.ALL)
AutoIncrementTracker autoIncr;
@OneToOne(mappedBy="table", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
UserView view;
@OneToMany(cascade=CascadeType.ALL, mappedBy="table")
List<UserTrigger> triggers = new ArrayList<UserTrigger>();
// engine can be nullable for views
@Column(name = "engine", nullable=true)
String engine = null;
@Column(name = "collation", nullable=true)
String collation;
@Column(name = "row_format", nullable=true)
String rowFormat;
@Column(name = "comment", nullable=true, length=2048)
String comment;
@Column(name = "create_options", nullable=true)
String createOptions;
transient StorageGroup storageGroup = null;
transient Map<String,UserColumn> userColumnMap = null;
transient ColumnSet showColumnSet = null;
transient Integer rangeID = null;
@ForeignKey(name="fk_table_container")
@ManyToOne(optional = true)
@JoinColumn(name = "container_id")
Container container;
// ostensibly I can figure this out based on what database this table is part of
// (if info schema/mysql schema this should be SYSTEM VIEW) and whether there is
// an associated view for this table, but I can't do it in such a way that I don't break
// info schema entity queries....so shelving it for now.
@Column(name = "table_type", nullable = false)
String table_type;
private static final long serialVersionUID = 1L;
private UserTable(String name, String createTableStmt,
Shape shape,
DistributionModel distributionModel, PersistentGroup persistentGroup,
UserDatabase userDatabase, List<Key> keys,
List<UserColumn> userColumns, AutoIncrementTracker autoIncr,
TableState state,
String engine,
String tableType) {
super();
this.name = name;
this.shape = shape;
this.createTableStmt = createTableStmt;
this.distributionModel = distributionModel;
this.persistentGroup = persistentGroup;
this.userDatabase = userDatabase;
this.userColumns = userColumns;
this.autoIncr = autoIncr;
this.container = null;
this.state = state;
this.engine = engine;
this.table_type = tableType;
}
public UserTable() {
super();
}
public UserTable(String name, DistributionModel dmodel,
UserDatabase userDatabase, TableState state, String engine, String tableType) {
this.name = name;
this.distributionModel = dmodel;
this.userDatabase = userDatabase;
this.container = null;
this.state = state;
this.engine = engine;
this.table_type = tableType;
}
public UserTable(String name, PersistentGroup sg, DistributionModel dm,
UserDatabase db, TableState state, String engine, String tableType) {
this(name, dm, db, state, engine, tableType);
this.persistentGroup = sg;
this.container = null;
}
public UserTable shallowClone() {
return new UserTable(name, createTableStmt, shape, distributionModel, persistentGroup, userDatabase, keys, getUserColumns(), autoIncr, state, engine, table_type);
}
@Override
public int getId() {
return this.id;
}
@Override
public String getName() {
return this.name;
}
private transient String qname = null;
public String getQualifiedName() {
if (qname == null)
qname = userDatabase.getName() + "." + getName();
return qname;
}
public String getNameAsIdentifier() {
return Singletons.require(DBNative.class).getNameForQuery(this);
}
public void setName(String n) {
this.name = n;
}
public String getCreateTableStmt(boolean tempTable, boolean useSystemTemp) throws PEException {
// TODO this method should really be pushed into DBNative. We should probably
// also distinguish between user and temp tables.
if (createTableStmt == null) {
StringBuffer createSql = new StringBuffer().append("CREATE ");
if (useSystemTemp)
createSql.append("TEMPORARY ");
createSql.append("TABLE ")
.append(Singletons.require(DBNative.class).getNameForQuery(this))
.append(" (")
.append(Singletons.require(DBNative.class).getColumnDefForQuery(getUserColumns().get(0)));
for (int i = 1; i < getUserColumns().size(); ++i) {
createSql.append(",").append(Singletons.require(DBNative.class).getColumnDefForQuery(getUserColumns().get(i)));
}
if (tempTable) {
for(Key k : keys) {
createSql.append(", ");
k.printTempTableKey(createSql);
}
}
createSql.append(") DEFAULT CHARSET=UTF8");
if (tempTable)
// createSql.append(" ENGINE=MEMORY");
createSql.append(" ENGINE=MYISAM");
createTableStmt = createSql.toString();
}
return createTableStmt;
}
public String getCreateTableStmt(boolean tempTable) throws PEException {
return getCreateTableStmt(tempTable, false);
}
public String getCreateTableStmt() throws PEException {
return getCreateTableStmt(false);
}
public void setCreateTableStmt(String createTableStmt) {
this.createTableStmt = createTableStmt;
}
public void setShape(Shape targ) {
this.shape = targ;
}
public Shape getShape() {
return this.shape;
}
public static SQLCommand getDropTableStmt(final VariableStoreSource vs, String tableName, boolean ifExists) {
return new SQLCommand(vs, "DROP TABLE " + (ifExists ? "IF EXISTS " : "")
+ Singletons.require(DBNative.class).quoteIdentifier(tableName));
}
public void addUserColumn(UserColumn uc) {
int order = 1;
if ( userColumnMap == null ) {
buildUserColumnMap();
}
if ( getUserColumns().size() > 0 ) {
order = getUserColumns().get(getUserColumns().size()-1).getOrderInTable() + 1;
}
uc.setOrderInTable(order);
getUserColumns().add(uc);
userColumnMap.put(uc.getAliasName(), uc);
}
public void removeUserColumn(UserColumn uc) {
getUserColumns().remove(uc);
if (userColumnMap != null) {
userColumnMap.remove(uc);
}
}
public void addUserColumnList(List<? extends UserColumn> ucs) {
if ( userColumnMap == null )
buildUserColumnMap();
for ( UserColumn uc : ucs )
addUserColumn( uc );
}
public void addColumnMetadataList(List<? extends ColumnMetadata> cms) throws PEException {
if ( userColumnMap == null )
buildUserColumnMap();
for ( ColumnMetadata cm : cms ) {
UserColumn uc = new UserColumn(cm);
if ( cm.usingAlias() )
uc.setName(cm.getAliasName());
addUserColumn(uc);
}
}
public UserColumn getUserColumn(String name) {
if ( userColumnMap == null )
buildUserColumnMap();
return userColumnMap.get(name);
}
public DistributionModel getDistributionModel() {
return this.distributionModel;
}
public void setDistributionModel(DistributionModel distributionModel) {
this.distributionModel = distributionModel;
}
public PersistentGroup getPersistentGroup() {
if (persistentGroup == null)
throw new PECodingException("Detected attempt to use non-persistent persistent group in persistent context");
return this.persistentGroup;
}
public void setPersistentGroup(PersistentGroup storageGroup) {
this.persistentGroup = storageGroup;
}
public StorageGroup getStorageGroup() {
return storageGroup;
}
public void setStorageGroup(StorageGroup storageGroup) {
this.storageGroup = storageGroup;
}
public final List<UserColumn> getUserColumns() {
return userColumns;
}
public final List<Key> getKeys() {
return keys;
}
public final Key getPrimaryKey() {
for(Key k : keys) {
if (k.isPrimaryKey())
return k;
}
return null;
}
public void addKey(Key k) {
keys.add(k);
}
public void removeKey(Key k) {
keys.remove(k);
}
public void addReferring(Key k) {
referencing.add(k);
}
public void removeReferring(Key k) {
referencing.remove(k);
}
public final Set<Key> getReferringKeys() {
return referencing;
}
public UserDatabase getDatabase() {
return userDatabase;
}
public void setDatabase(UserDatabase database) {
userDatabase = database;
}
@Override
public String toString() {
return "Table id: " + getId() + " Name: " + getName()
+ " with Distribution: " + getDistributionModel()
+ " on Storage Class: " + getStorageGroup()
+ " contained in: " + (container == null ? "null" : container.getId())
+ " state: " + state;
}
public static UserTable newTempTable(UserDatabase database,
ColumnSet shape, String tableName, DistributionModel distModel)
throws PEException {
UserTable tempTable = new UserTable(tableName, null, distModel, database, TableState.FIXED, "MYISAM", "BASE TABLE");
if (shape != null)
tempTable.addColumnMetadataList(shape.getColumnList());
return tempTable;
}
public static String getNewTempTableName() {
return TEMP_TABLE_PREFIX + GroupManager.getCoordinationServices().getGloballyUniqueId(TEMP_TABLE_PREFIX);
}
public boolean isTempTable() {
return name.startsWith(TEMP_TABLE_PREFIX);
}
public KeyTemplate getDistKey() {
KeyTemplate distKey = new KeyTemplate();
for (UserColumn col : getUserColumns()) {
if (col.getHashPosition() > 0)
distKey.add(col);
}
Collections.sort(distKey, new Comparator<UserColumn>() {
@Override
public int compare(UserColumn o1, UserColumn o2) {
return o1.getHashPosition() - o2.getHashPosition();
}
});
return distKey;
}
public KeyValue getDistValue(CatalogDAO c) throws PEException {
KeyValue dv = new KeyValue(this,getRangeID(c));
for (UserColumn col : getDistKey()) {
dv.addColumnTemplate(col);
}
return dv;
}
@Override
public long getNextIncrValue(CatalogDAO c) {
return autoIncr.getNextValue(c);
}
@Override
public long getNextIncrBlock(CatalogDAO c, long blockSize) {
return autoIncr.getIdBlock(c, blockSize);
}
public long readNextIncrValue(CatalogDAO c) {
return autoIncr.readNextValue(c);
}
@Override
public void removeNextIncrValue(CatalogDAO c, long value) {
autoIncr.removeValue(c, value);
}
public String displayName() {
return (userDatabase == null ? getName() : userDatabase.getName() + "." + getName());
// return userDatabase.getName()+"."+getName();
}
public void prepareGenerationAddition(ExecutionState estate, WorkerGroup wg, StorageGroupGeneration newGen, SQLCommand command) throws Throwable {
Set<PersistentSite> netNewSites = new HashSet<PersistentSite>(newGen.getStorageSites());
netNewSites.removeAll(wg.getStorageSites());
PersistentGroup newSG = new PersistentGroup(netNewSites);
SSConnection ssCon = estate.getConnection();
Manager wgManager = wg.setManager(null);
WorkerGroup newWG = WorkerGroupFactory.newInstance(ssCon, newSG, userDatabase);
newWG.assureDatabase(ssCon, userDatabase);
try {
if (command == null && getView() != null && getView().getViewMode() == ViewMode.EMULATE) {
// nothing to do
} else {
QueryStepDDLOperation qso =
new QueryStepDDLOperation(newSG, getDatabase(), (command == null ? new SQLCommand(ssCon,getCreateTableStmt()) : command), null);
qso.executeSelf(estate, newWG, DBEmptyTextResultConsumer.INSTANCE);
}
if (getView() == null)
distributionModel.prepareGenerationAddition(estate, wg, this, newGen);
} finally {
WorkerGroupFactory.purgeInstance(ssCon, newWG);
wg.setManager(wgManager);
}
}
private void buildUserColumnMap() {
userColumnMap = new HashMap<String, UserColumn>();
for (UserColumn col : getUserColumns()) {
userColumnMap.put(col.getAliasName(), col);
}
}
public void addAutoIncr() {
addAutoIncr(null);
}
public void addAutoIncr(Long offset) {
if (autoIncr == null) {
autoIncr = new AutoIncrementTracker(this, offset);
}
}
@Override
public boolean hasAutoIncr() {
return autoIncr != null;
}
public AutoIncrementTracker getAutoIncr() {
return autoIncr;
}
@Override
public ColumnSet getShowColumnSet(CatalogQueryOptions cqo) {
if ( showColumnSet == null ) {
showColumnSet = new ColumnSet();
showColumnSet.addColumn("Tables_in_" + this.userDatabase.getName(), 255, "varchar", Types.VARCHAR);
if (cqo.emitExtensions()) {
showColumnSet.addColumn("Distribution Model", 255, "varchar", Types.VARCHAR);
showColumnSet.addColumn(PersistentGroup.PERSISTENT_GROUP_CS_HEADER_VAL, 255, "varchar", Types.VARCHAR);
}
}
return showColumnSet;
}
@Override
public ResultRow getShowResultRow(CatalogQueryOptions cqo) {
ResultRow rr = new ResultRow();
String tabName = this.name;
/*
if (cqo.isTenant()) {
TableVisibility tv = getTenantVisibility();
if (tv != null && tv.getLocalName() != null)
tabName = tv.getLocalName();
}
*/
rr.addResultColumn(tabName, false);
if (cqo.emitExtensions()) {
rr.addResultColumn(this.distributionModel.getName());
rr.addResultColumn(this.persistentGroup.getName());
}
return rr;
}
public Container getContainer() {
return container;
}
public void setContainer(Container c) {
container = c;
}
public boolean isContainerBaseTable() {
if (container != null && container.getBaseTable() != null) {
return this.getId() == container.getBaseTable().getId();
}
return false;
}
public String getEngine() {
return engine;
}
public void setEngine(String e) {
engine = e;
}
public String getTableType() {
return table_type;
}
public String getCollation() {
return collation;
}
public void setCollation(String collation) {
this.collation = collation;
}
public String getRowFormat() {
return rowFormat;
}
public void setRowFormat(String v) {
rowFormat = v;
}
public String getComment() {
return comment;
}
public void setComment(String s) {
comment = s;
}
public String getCreateOptions() {
return createOptions;
}
public void setCreateOptions(String s) {
createOptions = s;
}
public final List<UserTrigger> getTriggers() {
return triggers;
}
@Override
public void removeFromParent() throws Throwable {
// distributionModel.removeUserTable(this);
userDatabase.removeUserTable(this);
}
@Override
public List<? extends CatalogEntity> getDependentEntities(CatalogDAO c) throws Throwable {
List<CatalogEntity> ceList = new ArrayList<CatalogEntity>();
if (view != null) {
ceList.addAll(view.getDependentEntities(c));
ceList.add(view);
}
RangeTableRelationship rtr = c.findRangeTableRelationship(this, false);
if ( rtr != null )
ceList.add(rtr);
for(Key k : keys) {
ceList.addAll(k.getDependentEntities(c));
ceList.add(k);
}
ceList.addAll(getUserColumns());
ceList.addAll(c.findTenantScopesForTable(this));
if (isContainerBaseTable()) {
ceList.add(getContainer());
}
for(UserTrigger ut : triggers) {
ceList.addAll(ut.getDependentEntities(c));
ceList.add(ut);
}
return ceList;
}
public void setState(TableState ts) {
state = ts;
}
public TableState getState() {
return state;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
UserTable other = (UserTable) obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
public SQLCommand getTruncateStatement(final VariableStoreSource vs) {
return new SQLCommand(vs, "TRUNCATE " + Singletons.require(DBNative.class).getNameForQuery(this));
}
public UserView getView() {
return view;
}
public void setView(UserView uv) {
view = uv;
}
@Override
public void onUpdate() {
}
@Override
public void onDrop() {
}
public boolean isSynthetic() {
return PEConstants.INFORMATION_SCHEMA_GROUP_NAME.equals(persistentGroup.getName())
|| PEConstants.SYSTEM_GROUP_NAME.equals(persistentGroup.getName());
}
@Override
public String getPersistentName() {
return getName();
}
@Override
public int getNumberOfColumns() {
return getUserColumns().size();
}
public Integer getRangeID(CatalogDAO c) throws PEException {
if (rangeID == null) synchronized(this){
if (distributionModel.getName().equals(ContainerDistributionModel.MODEL_NAME)) {
DistributionRange dr = getContainer().getRange();
if (dr != null)
rangeID = dr.getId();
} else if (distributionModel.getName().equals(RangeDistributionModel.MODEL_NAME)) {
rangeID = c.findRangeForTable(this).getId();
}
if (rangeID == null)
rangeID = -1;
}
if (rangeID == -1) return null;
return rangeID;
}
}