package com.zendesk.maxwell.schema;
import java.util.*;
import com.zendesk.maxwell.schema.ddl.InvalidSchemaError;
import com.zendesk.maxwell.schema.ddl.ColumnPosition;
import com.zendesk.maxwell.schema.columndef.IntColumnDef;
import com.zendesk.maxwell.schema.columndef.BigIntColumnDef;
import com.zendesk.maxwell.schema.columndef.EnumeratedColumnDef;
import org.apache.commons.lang3.StringUtils;
import com.zendesk.maxwell.schema.columndef.ColumnDef;
import com.zendesk.maxwell.schema.columndef.StringColumnDef;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Table {
public String database;
@JsonProperty("table")
public String name;
private TableColumnList columns;
public String charset;
private List<String> pkColumnNames;
private List<String> normalizedPKColumnNames;
private HashMap<String, Integer> columnOffsetMap;
@JsonIgnore
public int pkIndex;
public Table() { }
public Table(String database, String name, String charset, List<ColumnDef> list, List<String> pks) {
this.database = database;
this.name = name;
this.charset = charset;
this.setColumnList(list);
if ( pks == null )
pks = new ArrayList<String>();
this.setPKList(pks);
}
@JsonProperty("columns")
public List<ColumnDef> getColumnList() {
return columns.getList();
}
@JsonProperty("columns")
public void setColumnList(List<ColumnDef> list) {
this.columns = new TableColumnList(list);
}
@JsonIgnore
public List<StringColumnDef> getStringColumns() {
ArrayList<StringColumnDef> list = new ArrayList<>();
for ( ColumnDef c : columns ) {
if ( c instanceof StringColumnDef )
list.add((StringColumnDef) c);
}
return list;
}
public String getName() {
return this.name;
}
public int findColumnIndex(String name) {
return columns.indexOf(name);
}
public ColumnDef findColumn(String name) {
return columns.findByName(name);
}
@JsonIgnore
public int getPKIndex() {
return this.pkIndex;
}
public String getDatabase() {
return database;
}
public Table copy() {
ArrayList<ColumnDef> list = new ArrayList<>();
ArrayList<String> pkList = new ArrayList<>();
for ( ColumnDef c : columns ) {
list.add(c);
}
for ( String s : pkColumnNames ) {
pkList.add(s);
}
return new Table(database, name, charset, list, pkList);
}
public void rename(String tableName) {
this.name = tableName;
}
private void diffColumnList(List<String> diffs, Table a, Table b, String nameA, String nameB) {
for ( ColumnDef column : a.getColumnList() ) {
ColumnDef other = b.findColumn(column.getName());
if ( other == null )
diffs.add(b.fullName() + " is missing column " + column.getName() + " in " + nameB);
else {
String colName = a.fullName() + ".`" + column.getName() + "` ";
if ( !column.getType().equals(other.getType()) ) {
diffs.add(colName + "has a type mismatch, "
+ column.getType()
+ " vs "
+ other.getType()
+ " in " + nameB);
} else if ( column.getPos() != other.getPos() ) {
diffs.add(colName + "has a position mismatch, "
+ column.getPos()
+ " vs "
+ other.getPos()
+ " in " + nameB);
}
if ( column instanceof EnumeratedColumnDef ) {
EnumeratedColumnDef enumA, enumB;
enumA = (EnumeratedColumnDef) column;
enumB = (EnumeratedColumnDef) other;
if ( !Arrays.deepEquals(enumA.getEnumValues(), enumB.getEnumValues()) ) {
diffs.add(colName + "has an enum value mismatch, "
+ StringUtils.join(enumA.getEnumValues(), ",")
+ " vs "
+ StringUtils.join(enumB.getEnumValues(), ",")
+ " in " + nameB);
}
}
if ( column instanceof StringColumnDef ) {
StringColumnDef stringA, stringB;
stringA = (StringColumnDef) column;
stringB = (StringColumnDef) other;
if ( !Objects.equals(stringA.getCharset(), stringB.getCharset()) ) {
diffs.add(colName + "has an charset mismatch, "
+ "'" + stringA.getCharset() + "'"
+ " vs "
+ "'" + stringB.getCharset() + "'"
+ " in " + nameB);
}
}
if ( column instanceof IntColumnDef ) {
boolean signedA, signedB;
signedA = ((IntColumnDef) column).isSigned();
signedB = ((IntColumnDef) other).isSigned();
if ( signedA != signedB )
diffs.add(colName + "has a signedness mismatch, "
+ "'" + signedA + "'"
+ " vs "
+ "'" + signedB + "'"
+ " in " + nameB);
}
if ( column instanceof BigIntColumnDef ) {
boolean signedA, signedB;
signedA = ((BigIntColumnDef) column).isSigned();
signedB = ((BigIntColumnDef) other).isSigned();
if ( signedA != signedB )
diffs.add(colName + "has a signedness mismatch, "
+ "'" + signedA + "'"
+ " vs "
+ "'" + signedB + "'"
+ " in " + nameB);
}
}
}
}
public String fullName() {
return "`" + this.database + "`." + this.name + "`";
}
public void diff(List<String> diffs, Table other, String nameA, String nameB) {
if ( !this.getCharset().equals(other.getCharset()) ) {
diffs.add(this.fullName() + " differs in charset: "
+ nameA + " is " + this.getCharset() + " but "
+ nameB + " is " + other.getCharset());
}
if ( !this.getPKString().equals(other.getPKString())) {
diffs.add(this.fullName() + " differs in PKs: "
+ nameA + " is " + this.getPKString() + " but "
+ nameB + " is " + other.getPKString());
}
if ( !this.getName().equals(other.getName()) ) {
diffs.add(this.fullName() + " differs in name: "
+ nameA + " is " + this.getName() + " but "
+ nameB + " is " + other.getName());
}
diffColumnList(diffs, this, other, nameA, nameB);
diffColumnList(diffs, other, this, nameB, nameA);
}
public void setDefaultColumnCharsets() {
for ( StringColumnDef c : getStringColumns() ) {
c.setDefaultCharset(this.getCharset());
}
}
public void addColumn(int index, ColumnDef definition) {
columns.add(index, definition);
}
public void addColumn(ColumnDef definition) {
columns.add(columns.size(), definition);
}
public void removeColumn(int idx) {
ColumnDef toRemove = columns.get(idx);
removePKColumn(toRemove.getName());
columns.remove(idx);
}
public void changeColumn(int idx, ColumnPosition position, ColumnDef definition) throws InvalidSchemaError {
// when we go to rename the PK column, we need to make sure the old column name
// is still there for (for normalization of pk-columns).
ColumnDef oldColumn = columns.get(idx);
renamePKColumn(oldColumn.getName(), definition.getName());
columns.remove(idx);
columns.add(position.index(this, idx), definition);
}
public void setDatabase(String database) {
this.database = database;
}
public String getCharset() {
return charset;
}
@JsonProperty("primary-key")
public List<String> getPKList() {
return normalizedColumnNames();
}
@JsonIgnore
public String getPKString() {
if ( this.pkColumnNames != null )
return StringUtils.join(pkColumnNames.iterator(), ",");
else
return null;
}
@JsonProperty("primary-key")
public synchronized void setPKList(List<String> pkColumnNames) {
this.pkColumnNames = pkColumnNames;
this.normalizedPKColumnNames = null;
}
private synchronized void removePKColumn(String name) {
int pkIndex = getPKList().indexOf(name);
if ( pkIndex != -1 ) {
this.pkColumnNames.remove(pkIndex);
this.normalizedPKColumnNames = null;
}
}
private synchronized void renamePKColumn(String oldName, String newName) {
int pkIndex = getPKList().indexOf(oldName);
if ( pkIndex != -1 ) {
this.pkColumnNames.set(pkIndex, newName);
this.normalizedPKColumnNames = null;
}
}
private synchronized List<String> normalizedColumnNames() {
/*
primary keys may come in with different casing than the column names.
convert the list of primary keys to match the column casing.
we do this normalization lazily, as when a Table object is being deserialized
from JSON, there may be no column definitions present when the setPKList() function is called.
ugly!
*/
if ( this.normalizedPKColumnNames == null ) {
this.normalizedPKColumnNames = new ArrayList<>(this.pkColumnNames.size());
for (String name : pkColumnNames) {
ColumnDef cd = findColumn(name);
if ( cd == null )
throw new RuntimeException("Couldn't find column for primary-key: " + name);
this.normalizedPKColumnNames.add(cd.getName());
}
}
return this.normalizedPKColumnNames;
}
}