package com.tesora.dve.sql.schema;
/*
* #%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.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tesora.dve.db.DBNative;
import org.apache.commons.lang.StringUtils;
import com.tesora.dve.common.catalog.CatalogEntity;
import com.tesora.dve.common.catalog.ConstraintType;
import com.tesora.dve.common.catalog.PersistentColumn;
import com.tesora.dve.common.catalog.UserColumn;
import com.tesora.dve.common.catalog.UserTable;
import com.tesora.dve.db.mysql.MysqlNativeType.MysqlType;
import com.tesora.dve.db.mysql.common.ColumnAttributes;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.resultset.ResultRow;
import com.tesora.dve.server.global.HostService;
import com.tesora.dve.singleton.Singletons;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.expression.Traversable;
import com.tesora.dve.sql.node.expression.ActualLiteralExpression;
import com.tesora.dve.sql.node.expression.ColumnInstance;
import com.tesora.dve.sql.node.expression.ExpressionNode;
import com.tesora.dve.sql.node.expression.IdentifierLiteralExpression;
import com.tesora.dve.sql.node.expression.LiteralExpression;
import com.tesora.dve.sql.node.expression.TableInstance;
import com.tesora.dve.sql.schema.modifiers.CharsetTableModifier;
import com.tesora.dve.sql.schema.modifiers.CollationTableModifier;
import com.tesora.dve.sql.schema.modifiers.ColumnKeyModifier;
import com.tesora.dve.sql.schema.modifiers.ColumnModifier;
import com.tesora.dve.sql.schema.modifiers.ColumnModifierKind;
import com.tesora.dve.sql.schema.modifiers.DefaultValueModifier;
import com.tesora.dve.sql.schema.mt.TenantColumn;
import com.tesora.dve.sql.schema.types.BasicType;
import com.tesora.dve.sql.schema.types.BasicType.SerialPlaceholderType;
import com.tesora.dve.sql.schema.types.TextType;
import com.tesora.dve.sql.schema.types.Type;
import com.tesora.dve.sql.transform.CopyContext;
import com.tesora.dve.sql.util.Accessor;
import com.tesora.dve.sql.util.Functional;
import com.tesora.dve.sql.util.ListSet;
public class PEColumn extends Persistable<PEColumn, UserColumn>
implements HasComment, TableComponent<PEColumn>, Column<PEAbstractTable<?>>, PersistentColumn {
static final String CURRENT_TIMESTAMP = "CURRENT_TIMESTAMP";
protected static final String ON_UPDATE_TAG = "ON UPDATE CURRENT_TIMESTAMP";
protected Type type;
protected short flags = 0;
protected ExpressionNode defaultValue = null;
protected int dvposition = 0;
protected int cdv_position = 0;
protected PEAbstractTable<?> ofTable;
protected int position = -1;
private Comment comment = null;
public static PEColumn buildColumn(SchemaContext sc, Name name, Type type, List<ColumnModifier> modifiers, Comment comment, List<ColumnKeyModifier> inlineKeys) {
ExpressionNode defaultValue = null;
short f = (short)type.getColumnAttributesFlags();
boolean explicitNullability = false;
for(Iterator<ColumnModifier> iter = modifiers.iterator(); iter.hasNext();) {
ColumnModifier cm = iter.next();
int was = f;
if (cm.getTag() == ColumnModifierKind.AUTOINCREMENT) {
f = ColumnAttributes.set(f, ColumnAttributes.AUTO_INCREMENT);
} else if (cm.getTag() == ColumnModifierKind.NOT_NULLABLE) {
f = ColumnAttributes.set(f, ColumnAttributes.NOT_NULLABLE);
explicitNullability = true;
} else if (cm.getTag() == ColumnModifierKind.NULLABLE) {
iter.remove();
explicitNullability = true;
} else if (cm.getTag() == ColumnModifierKind.ONUPDATE) {
f = ColumnAttributes.set(f, ColumnAttributes.ONUPDATE);
} else if (cm.getTag() == ColumnModifierKind.DEFAULTVALUE) {
DefaultValueModifier dvm = (DefaultValueModifier) cm;
defaultValue = dvm.getDefaultValue();
f = ColumnAttributes.set(f, ColumnAttributes.HAS_DEFAULT_VALUE);
}
if (was != f)
iter.remove();
}
if (!modifiers.isEmpty())
throw new SchemaException(Pass.SECOND, "Unhandled column modifier: " + modifiers.get(0).getTag().getSQL());
if (type.isTimestampType() && !explicitNullability) {
f = ColumnAttributes.set(f, ColumnAttributes.ONUPDATE);
f = ColumnAttributes.set(f, ColumnAttributes.NOT_NULLABLE);
}
if (type.isSerialPlaceholder()) {
// serial = auto increment notn ull
f = ColumnAttributes.set(f, ColumnAttributes.NOT_NULLABLE);
f = ColumnAttributes.set(f, ColumnAttributes.AUTO_INCREMENT);
type = ((SerialPlaceholderType)type).convert();
if (inlineKeys != null)
inlineKeys.add(new ColumnKeyModifier(ConstraintType.UNIQUE));
}
PEColumn e = new PEColumn(sc,name,type,f,defaultValue,comment);
e.normalize();
return e;
}
protected PEColumn(SchemaContext pc, Name name, Type type, short givenFlags, ExpressionNode defVal, Comment theComment) {
super(null);
setName(name);
setPersistent(pc,null,null);
this.type = type;
this.flags = givenFlags;
this.comment = theComment;
this.defaultValue = defVal;
}
// for placeholder columns in temp tables
public PEColumn(SchemaContext pc,Name name, Type type) {
this(pc, name, type, (short)0, null, null);
}
public static PEColumn load(UserColumn persistent, SchemaContext pc, PETable enclosingTable) {
PEColumn e = null;
if (enclosingTable != null) {
e = enclosingTable.lookup(pc, new UnqualifiedName(persistent.getName()));
}
if (e == null) {
if (TenantColumn.TENANT_COLUMN.equals(persistent.getName()))
e = new TenantColumn(persistent, pc);
else {
e = new PEColumn(persistent, pc,true);
}
}
return e;
}
protected void setFlag(short f) {
flags = ColumnAttributes.set(flags, f);
}
protected void clearFlag(short f) {
flags = ColumnAttributes.clear(flags, f);
}
protected boolean isSet(short f) {
return ColumnAttributes.isSet(flags, f);
}
protected PEColumn(UserColumn persistent, SchemaContext pc, boolean loading) {
super(null);
if (loading)
pc.startLoading(this, persistent);
setName(new UnqualifiedName(persistent.getName(),true));
this.type = BasicType.buildType(persistent, pc.getTypes());
flags = (short) persistent.getFlags();
if (persistent.hasDefault()) {
String defVal = persistent.getDefaultValue();
ExpressionNode defLiteral = null;
if (defVal == null)
defLiteral = LiteralExpression.makeNullLiteral();
else {
/* we have a bunch of different cases here:
* timestamp type: could have current_timestamp, could have a literal (in quotes)
* string type: could have a default of 'null'
* integral types: could have a default of 1 or '1'
* etc.
*/
if (type.isTimestampType()) {
if (CURRENT_TIMESTAMP.equalsIgnoreCase(defVal.trim())) {
defLiteral = new IdentifierLiteralExpression(new UnqualifiedName(CURRENT_TIMESTAMP));
} else if (StringUtils.equals("0", defVal)) {
defLiteral = new IdentifierLiteralExpression(new UnqualifiedName("0"));
}
}
if (defLiteral == null)
defLiteral = new ActualLiteralExpression(defVal,null);
}
defaultValue = defLiteral;
}
this.dvposition = persistent.getHashPosition();
this.cdv_position = persistent.getCDV_Position();
if (loading) {
setPersistent(pc,persistent,persistent.getId());
pc.finishedLoading(this, persistent);
}
}
// used in view support - build a transient column from a persistent one
public static PEColumn build(SchemaContext sc, UserColumn uc) {
return new PEColumn(uc,sc,false);
}
@Override
public Type getType() { return type; }
@Override
public PEAbstractTable<?> getTable() { return ofTable; }
@Override
public void setTable(PEAbstractTable<?> t) {
ofTable = t;
}
@Override
public void setComment(Comment s) { comment = s; }
@Override
public Comment getComment() { return comment; }
// needed for insert support
public boolean isNullable() {
return !isSet(ColumnAttributes.NOT_NULLABLE);
}
// needed for primary key support - columns in primary keys are not nullable
public void makeNotNullable() {
setFlag(ColumnAttributes.NOT_NULLABLE);
// if the default value is null - clear that
if (defaultValue instanceof LiteralExpression) {
LiteralExpression litex = (LiteralExpression) defaultValue;
if (litex.isNullLiteral())
defaultValue = null;
}
}
public boolean isNotNullable() {
return isSet(ColumnAttributes.NOT_NULLABLE);
}
public ExpressionNode getDefaultValue() {
return defaultValue;
}
public boolean isAutoIncrement() {
return isSet(ColumnAttributes.AUTO_INCREMENT);
}
public boolean isOnUpdated() {
return isSet(ColumnAttributes.ONUPDATE);
}
public boolean hasDefault() {
return isSet(ColumnAttributes.HAS_DEFAULT_VALUE);
}
public void makeAutoincrement() {
setFlag(ColumnAttributes.AUTO_INCREMENT);
}
public void clearAutoIncrement() {
clearFlag(ColumnAttributes.AUTO_INCREMENT);
}
// alter support
public void setDefaultValue(LiteralExpression litex) {
defaultValue = litex;
if (litex == null)
clearFlag(ColumnAttributes.HAS_DEFAULT_VALUE);
}
public int getDistributionValuePosition() { return dvposition; }
public void setDistributionValuePosition(int v) { dvposition = v; }
public boolean isPartOfDistributionVector() { return dvposition > 0; }
public boolean isCompleteDistributionVector(SchemaContext sc) {
return ofTable.getDistributionVector(sc).isComplete(sc,this);
}
public int getContainerDistributionValuePosition() { return cdv_position; }
public void setContainerDistributionValuePosition(int v) { cdv_position = v; }
public boolean isPartOfContainerDistributionVector() { return cdv_position > 0; }
public boolean comparableForDistribution(SchemaContext sc, PEColumn other) {
UserColumn backing = getPersistent(sc, false);
UserColumn otherBacking = other.getPersistent(sc, false);
if (backing != null && otherBacking != null)
return backing.comparableType(otherBacking);
return getType().comparableForDistribution(other.getType());
}
@Override
public boolean isTenantColumn() {
return false;
}
public boolean isTempColumn() {
return false;
}
public ExpressionNode buildProjection(TableInstance ti) {
return new ColumnInstance(getName(), this, ti);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((type == null) ? 0 : type.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;
PEColumn other = (PEColumn) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
return true;
}
private boolean typeDiffers(SchemaContext sc, List<String> messages, PEColumn other, boolean first, @SuppressWarnings("rawtypes") Set<Persistable> visited) {
if (maybeBuildDiffMessage(sc, messages, "type", getType().getName(), other.getType().getName(), first, visited))
return true;
if (maybeBuildDiffMessage(sc, messages, "has size", getType().declUsesSizing(), other.getType().declUsesSizing(), first, visited))
return true;
if (maybeBuildDiffMessage(sc, messages, "type size", getType().getSize(), other.getType().getSize(), first, visited))
return true;
return false;
}
@Override
public boolean collectDifferences(SchemaContext sc, List<String> messages, Persistable<PEColumn, UserColumn> oth,
boolean first, @SuppressWarnings("rawtypes") Set<Persistable> visited) {
PEColumn other = oth.get();
if (visited.contains(this) && visited.contains(other)) {
return false;
}
visited.add(this);
visited.add(other);
if (maybeBuildDiffMessage(sc, messages, "name", getName(), other.getName(), first, visited))
return true;
if (typeDiffers(sc, messages, other, first, visited))
return true;
if (flags != other.flags) {
if (maybeBuildDiffMessage(sc, messages, "column modifiers", flags, other.flags, first, visited)) {
return true;
}
}
if (maybeBuildDiffMessage(sc, messages, "position", this.getPosition(), other.getPosition(), first, visited)) {
return true;
}
if (maybeBuildDiffMessage(sc, messages, "distribution vector position",
new Integer(getDistributionValuePosition()), new Integer(other.getDistributionValuePosition()), first, visited)) {
return true;
}
if (maybeBuildDiffMessage(sc, messages, "default value", this.getDefaultValue(), other.getDefaultValue(), first, visited)) {
return true;
}
return false;
}
@Override
protected String getDiffTag() {
return "Column " + getName().getSQL();
}
public static Map<Name, PEColumn> buildNameMap(Collection<PEColumn> in) {
return Functional.buildMap(in, new Accessor<Name, PEColumn>() {
@Override
public Name evaluate(PEColumn object) {
return object.getName();
}
});
}
@Override
public Persistable<PEColumn, UserColumn> reload(
SchemaContext usingContext) {
throw new IllegalStateException("Cannot reload a lone column");
}
@Override
public String toString() {
return String.valueOf(ofTable).concat(".").concat(getName().get());
}
@Override
protected int getID(UserColumn p) {
return p.getId();
}
@Override
protected UserColumn lookup(SchemaContext sc) throws PEException {
UserTable ut = ofTable.persistTree(sc);
String persName = Singletons.require(DBNative.class).getEmitter().getPersistentName(this);
return ut.getUserColumn(persName);
}
@Override
protected Persistable<PEColumn, UserColumn> load(SchemaContext sc, UserColumn p)
throws PEException {
return new PEColumn(p,sc,true);
}
@Override
protected UserColumn createEmptyNew(SchemaContext pc) throws PEException {
String persName = Singletons.require(DBNative.class).getEmitter().getPersistentName(this);
UserColumn uc = Singletons.require(DBNative.class).updateUserColumn(null, type);
uc.setName(persName);
pc.getSaveContext().add(this,uc);
return uc;
}
/**
* @param sc
* @param uc
* @throws PEException
*/
// this function just seems messed up
private void setPersistentDefaultValue(SchemaContext sc, UserColumn uc) throws PEException {
if (defaultValue == null) {
MysqlType mt = MysqlType.toMysqlType(uc.getTypeName());
if (mt == MysqlType.TIMESTAMP) {
setFlag(ColumnAttributes.HAS_DEFAULT_VALUE);
if (isNullable()) {
uc.setDefaultValue(null);
} else {
if (isSet(ColumnAttributes.ONUPDATE)) {
uc.setDefaultValue("0");
} else {
uc.setDefaultValue(CURRENT_TIMESTAMP);
}
}
} else {
clearFlag(ColumnAttributes.HAS_DEFAULT_VALUE);
uc.setDefaultValue(null);
}
} else {
setFlag(ColumnAttributes.HAS_DEFAULT_VALUE);
LiteralExpression le = (LiteralExpression)defaultValue;
if (le.isNullLiteral()) {
uc.setDefaultValue(null);
} else {
uc.setDefaultValue(le.getValue(sc.getValues()).toString());
}
}
}
public void takeCharsetSettings(CharsetTableModifier charset, CollationTableModifier collation, boolean update) {
if (!isTempColumn() && hasStringType()) {
final TextType typeAsText = type.toTextType();
if (update || (typeAsText.getCharset() == null)) {
typeAsText.setCharset(charset.getCharset());
}
if (update || (typeAsText.getCollation() == null)) {
typeAsText.setCollation(collation.getCollation());
}
type = typeAsText;
}
}
public boolean hasStringType() {
return type.isStringType();
}
public UnqualifiedName getCharset() {
if (type instanceof TextType) {
TextType tt = (TextType) type;
if (tt.getCharset() == null) return null;
if (getTable() == null) return tt.getCharset();
if (getTable().getCharset() == null) return tt.getCharset();
if (tt.getCharset().equals(getTable().getCharset().getCharset())) return null;
return tt.getCharset();
}
return null;
}
public boolean shouldEmitCharset() {
if (type instanceof TextType) {
final TextType tt = (TextType) type;
final UnqualifiedName charset = tt.getCharset();
if (charset != null) {
if (tt.isBinaryText()) {
return true;
}
final PEAbstractTable<?> parent = this.getTable();
if (parent != null) {
final CharsetTableModifier tableCharset = parent.getCharset();
if (tableCharset != null) {
return !charset.equals(tableCharset.getCharset());
}
}
}
}
return false;
}
public UnqualifiedName getCollation() {
if (type instanceof TextType) {
TextType tt = (TextType)type;
if (tt.getCollation() == null) return null;
if (getTable() == null) return tt.getCollation();
if (getTable().getCollation() == null) return tt.getCollation();
if (tt.getCollation().equals(getTable().getCollation().getCollation())) return null;
return tt.getCollation();
}
return null;
}
public boolean shouldEmitCollation() {
if (type instanceof TextType) {
final TextType tt = (TextType) type;
final UnqualifiedName collation = tt.getCollation();
if (collation != null) {
final PEAbstractTable<?> parent = this.getTable();
if (parent != null) {
final CollationTableModifier tableCharset = parent.getCollation();
if (tableCharset != null) {
return !collation.equals(tableCharset.getCollation());
}
}
}
}
return false;
}
public void makeBinaryText() {
if (type instanceof TextType) {
TextType tt = (TextType) type;
tt.makeBinaryText();
}
}
private void addTypeModifiers(UserColumn uc) {
uc.setFlags(flags);
}
@Override
protected void populateNew(SchemaContext pc, UserColumn uc) throws PEException {
UserTable ut = ofTable.persistTree(pc);
if (isAutoIncrement()) {
uc.setAutoGenerated(Boolean.TRUE);
Long offset = getTable().asTable().getAutoIncOffset(pc);
pc.getCatalog().addAutoIncrement(pc,getTable().asTable(), offset);
}
uc.setUserTable(ut);
updateExisting(pc,uc);
}
@Override
protected void updateExisting(SchemaContext pc, UserColumn uc) throws PEException {
setPersistentDefaultValue(pc,uc);
updateExistingInternal(pc,uc);
}
private void updateExistingInternal(SchemaContext pc, UserColumn uc) {
String persName = Singletons.require(DBNative.class).getEmitter().getPersistentName(this);
uc.setName(persName);
uc.setFlags(flags);
uc.setHashPosition(dvposition);
uc.setCDV_Position(cdv_position);
addTypeModifiers(uc);
Singletons.require(DBNative.class).updateUserColumn(uc, type);
}
@Override
protected Class<? extends CatalogEntity> getPersistentClass() {
return UserColumn.class;
}
@Override
public PEColumn getIn(SchemaContext pc, PEAbstractTable<?> tab) {
PEColumn e = tab.lookup(pc, getName());
return e;
}
@Override
public void take(SchemaContext pc, PEColumn targ) {
if (!getName().getUnqualified().getUnquotedName().equals(targ.getName().getUnqualified().getUnquotedName())) {
setName(targ.getName());
if (ofTable != null) {
ofTable.refreshColumnLookupTable();
}
}
type = targ.getType();
flags = targ.flags;
defaultValue = targ.getDefaultValue();
}
public void setType(final Type type) {
if (this.type != null) {
int oldFlags = this.type.getColumnAttributesFlags();
flags &= ~oldFlags;
}
this.type = type;
flags |= type.getColumnAttributesFlags();
}
@Override
public Traversable copy(SchemaContext pc, CopyContext cc) {
return new PEColumn(pc, getName(), getType(), flags, defaultValue, getComment());
}
public boolean isPrimaryKeyPart() {
return isSet(ColumnAttributes.PRIMARY_KEY_PART);
}
public void setPrimaryKeyPart() {
setFlag(ColumnAttributes.PRIMARY_KEY_PART);
setFlag(ColumnAttributes.KEY_PART);
}
public void clearPrimaryKeyPart() {
clearFlag(ColumnAttributes.PRIMARY_KEY_PART);
}
public boolean isUniquePart() {
return isSet(ColumnAttributes.UNIQUE_KEY_PART);
}
public void setUniqueKeyPart() {
setFlag(ColumnAttributes.UNIQUE_KEY_PART);
setFlag(ColumnAttributes.KEY_PART);
}
public void clearUniqueKeyPart() {
clearFlag(ColumnAttributes.UNIQUE_KEY_PART);
}
public boolean isKeyPart() {
return isSet(ColumnAttributes.KEY_PART);
}
public void setKeyPart() {
setFlag(ColumnAttributes.KEY_PART);
}
public void clearKeyPart() {
clearFlag(ColumnAttributes.KEY_PART);
}
public ListSet<PEKey> getReferencedBy(SchemaContext sc) {
ListSet<PEKey> out = new ListSet<PEKey>();
if (!isKeyPart()) return out;
for(PEKey pek : getTable().getKeys(sc)) {
if (pek.getConstraint() == ConstraintType.FOREIGN) continue;
for(PEKeyColumnBase pekc : pek.getKeyColumns()) {
if (pekc.getColumn().equals(this)) {
out.add(pek);
break;
}
}
}
return out;
}
@Override
public int getPosition() {
return position;
}
public void setPosition(int v) {
position = v;
}
public void normalize() {
this.normalize(false);
}
public void normalize(final boolean useImplicitNull) {
type = type.normalize();
if (defaultValue == null) {
if (type.isTimestampType() && isNotNullable()) {
if (isOnUpdated())
defaultValue = new IdentifierLiteralExpression(new UnqualifiedName(CURRENT_TIMESTAMP));
else
defaultValue = LiteralExpression.makeStringLiteral("0000-00-00 00:00:00");
flags = ColumnAttributes.set(flags, ColumnAttributes.HAS_DEFAULT_VALUE);
} else if (!type.supportsDefaultValue()) {
} else if (!isNotNullable() && !useImplicitNull) {
defaultValue = LiteralExpression.makeNullLiteral();
flags = ColumnAttributes.set(flags, ColumnAttributes.HAS_DEFAULT_VALUE);
}
}
}
public Integer getIndexSize() {
return type.getIndexSize();
}
@Override
public String getPersistentName() {
return getName().getUnquotedName().get();
}
@Override
public String getAliasName() {
return getPersistentName();
}
@Override
public int getId() {
return getPersistentID();
}
@Override
public String getTypeName() {
return getType().getTypeName();
}
@Override
public int getHashPosition() {
return dvposition;
}
// for temporary tables
public ResultRow buildRow(SchemaContext sc) {
ResultRow rr = new ResultRow();
sc.beginSaveContext();
try {
rr.addResultColumn(getName().getUnquotedName().get());
UserColumn uc = new UserColumn();
updateExistingInternal(sc,uc);
rr.addResultColumn(Singletons.require(DBNative.class).getDataTypeForQuery(uc));
rr.addResultColumn(isNullable() ? "YES" : "NO");
String kp = "";
if (isPrimaryKeyPart())
kp = "PRI";
else if (isUniquePart())
kp = "UNI";
else if (isKeyPart())
kp = "MUL";
rr.addResultColumn(kp);
if (defaultValue == null || ((LiteralExpression)defaultValue).isNullLiteral())
rr.addResultColumn(null,true);
else
rr.addResultColumn(defaultValue.toString(sc));
rr.addResultColumn(isAutoIncrement() ? "auto_increment" : "");
return rr;
} catch (PEException pe) {
throw new SchemaException(Pass.SECOND, "Unable to build temporary table show columns result set",pe);
} finally {
sc.endSaveContext();
}
}
}