ru.curs.celesta.score.MaterializedView Maven / Gradle / Ivy
The newest version!
package ru.curs.celesta.score;
import ru.curs.celesta.event.TriggerType;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Materialized view data element.
*
* @author ioann
* @since 2017-06-08
*/
public final class MaterializedView extends AbstractView implements TableElement {
/**
* System field name that contains result of COUNT().
*/
public static final String SURROGATE_COUNT = "surrogate_count";
/**
* Checksum comment template.
*/
public static final String CHECKSUM_COMMENT_TEMPLATE = "/*CHECKSUM%sCHECKSUM*/";
static final Map>, MatColFabricFunction>
COL_CLASSES_AND_FABRIC_FUNCS = new HashMap<>();
final NamedElementHolder> pk = new NamedElementHolder<>() {
@Override
protected String getErrorMsg(String name) {
return String.format("Column '%s' defined more than once for primary key in table '%s'.", name, getName());
}
};
private final IntegerColumn surrogateCount;
private final NamedElementHolder> realColumns = new NamedElementHolder<>() {
@Override
protected String getErrorMsg(String name) {
return String.format("Column '%s' defined more than once in table '%s'.", name, getName());
}
};
public MaterializedView(GrainPart grainPart, String name) throws ParseException {
super(grainPart, name);
getGrain().addElement(this);
surrogateCount = new IntegerColumn(this, SURROGATE_COUNT);
surrogateCount.setNullableAndDefault(false, "0");
}
public IntegerColumn getSurrogateCount() {
return surrogateCount;
}
@FunctionalInterface
interface MatColFabricFunction {
Column> apply(MaterializedView mView, Column> colRef, String alias) throws ParseException;
}
static {
COL_CLASSES_AND_FABRIC_FUNCS.put(IntegerColumn.class,
(mView, colRef, alias) -> new IntegerColumn(mView, alias));
COL_CLASSES_AND_FABRIC_FUNCS.put(FloatingColumn.class,
(mView, colRef, alias) -> new FloatingColumn(mView, alias));
COL_CLASSES_AND_FABRIC_FUNCS.put(
DecimalColumn.class, (mView, colRef, alias) -> {
DecimalColumn dc = (DecimalColumn) colRef;
int precision = dc.getPrecision();
int scale = dc.getScale();
return new DecimalColumn(mView, alias, precision, scale);
}
);
COL_CLASSES_AND_FABRIC_FUNCS.put(BooleanColumn.class,
(mView, colRef, alias) -> new BooleanColumn(mView, alias));
COL_CLASSES_AND_FABRIC_FUNCS.put(BinaryColumn.class,
(mView, colRef, alias) -> new BinaryColumn(mView, alias));
COL_CLASSES_AND_FABRIC_FUNCS.put(DateTimeColumn.class,
(mView, colRef, alias) -> new DateTimeColumn(mView, alias));
COL_CLASSES_AND_FABRIC_FUNCS.put(ZonedDateTimeColumn.class,
(mView, colRef, alias) -> new ZonedDateTimeColumn(mView, alias));
COL_CLASSES_AND_FABRIC_FUNCS.put(StringColumn.class, (mView, colRef, alias) -> {
StringColumn result = new StringColumn(mView, alias);
StringColumn strColRef = (StringColumn) colRef;
result.setLength(String.valueOf(strColRef.getLength()));
return result;
});
}
@Override
String viewType() {
return "materialized view";
}
@Override
AbstractSelectStmt newSelectStatement() {
return new MaterializedSelectStmt(this);
}
@Override
public Map> getColumns() {
return realColumns.getElements();
}
public List getColumnRefNames() {
if (!getSegments().isEmpty()) {
List result = new ArrayList<>();
for (Map.Entry entry : getSegments().get(0).columns.entrySet()) {
Expr expr = entry.getValue();
if (!(expr instanceof Count)) {
Column> colRef = EXPR_CLASSES_AND_COLUMN_EXTRACTORS.get(expr.getClass()).apply(expr);
result.add(colRef.getName());
}
}
return result;
} else {
return Collections.emptyList();
}
}
@Override
public Column> getColumn(String colName) throws ParseException {
Column> result = realColumns.get(colName);
if (result == null) {
throw new ParseException(
String.format("Column '%s' not found in materialized view '%s.%s'",
colName, getGrain().getName(), getName()));
}
return result;
}
@Override
public void addColumn(Column> column) throws ParseException {
if (column.getParentTable() != this) {
throw new IllegalArgumentException();
}
getGrain().modify();
realColumns.addElement(column);
}
@Override
public synchronized void removeColumn(Column> column) throws ParseException {
// It's not allowed to delete compound part of the primary key
if (pk.contains(column)) {
throw new ParseException(
String.format(YOU_CANNOT_DROP_A_COLUMN_THAT_BELONGS_TO + "a primary key. Change primary key first.",
getGrain().getName(), getName(), column.getName()));
}
// It's not allowed to delete compound part of an index
for (Index ind : getGrain().getIndices().values()) {
if (ind.getColumns().containsValue(column)) {
throw new ParseException(String.format(
YOU_CANNOT_DROP_A_COLUMN_THAT_BELONGS_TO + "an index. Drop or change relevant index first.",
getGrain().getName(), getName(), column.getName()));
}
}
getGrain().modify();
realColumns.remove(column);
}
@Override
public boolean hasPrimeKey() {
return !pk.getElements().isEmpty();
}
@Override
public String getPkConstraintName() {
return limitName("pk_" + getName());
}
@Override
public Map> getPrimaryKey() {
return pk.getElements();
}
public TableRef getRefTable() {
return getSegments().get(0).tables.values().stream().findFirst().get();
}
public boolean isGroupByColumn(String alias) {
return getSegments().get(0).groupByColumns.containsKey(alias);
}
public String getSelectPartOfScript() {
SQLGenerator gen = new SQLGenerator();
StringWriter sw = new StringWriter();
PrintWriter bw = new PrintWriter(sw);
BWWrapper bww = new BWWrapper();
getSegments().get(0).writeSelectPart(bw, gen, bww);
bw.flush();
return sw.getBuffer().toString();
}
public String getGroupByPartOfScript() {
SQLGenerator gen = new SQLGenerator();
StringWriter sw = new StringWriter();
PrintWriter bw = new PrintWriter(sw);
getSegments().get(0).writeGroupByPart(bw, gen);
bw.flush();
return sw.getBuffer().toString();
}
@SuppressWarnings("EmptyStatement")
public String getChecksum() {
// TODO: CelestaSerializer is not intended to be used from GrainElement classes.
// Consider using a different approach for checksum calculation.
try (ChecksumInputStream is = new ChecksumInputStream(
new ByteArrayInputStream(CelestaSerializer.toString(this).getBytes(StandardCharsets.UTF_8))
)) {
while (is.read() != -1) ;
return String.format("%08X", is.getCRC32());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
public String getTriggerName(TriggerType type) {
TableElement t = getRefTable().getTable();
TriggerNameBuilder tnb = new TriggerNameBuilder()
.withSchema(getGrain().getName())
.withTableName(t.getName())
.withName(getName())
.withType(type);
return tnb.build();
}
static final class TriggerNameBuilder {
private static final Map TRIGGER_TYPES_TO_NAME_PARTS = new HashMap<>();
private static final String TEMPLATE = "mv%sFrom%s_%sTo%s_%s";
static {
TRIGGER_TYPES_TO_NAME_PARTS.put(TriggerType.POST_INSERT, "Insert");
TRIGGER_TYPES_TO_NAME_PARTS.put(TriggerType.POST_UPDATE, "Update");
TRIGGER_TYPES_TO_NAME_PARTS.put(TriggerType.POST_DELETE, "Delete");
}
private String schema;
private String tableName;
private String name;
private TriggerType type;
@SuppressWarnings("HiddenField")
public TriggerNameBuilder withTableName(String tableName) {
this.tableName = tableName;
return this;
}
@SuppressWarnings("HiddenField")
public TriggerNameBuilder withSchema(String schema) {
this.schema = schema;
return this;
}
@SuppressWarnings("HiddenField")
public TriggerNameBuilder withName(String name) {
this.name = name;
return this;
}
@SuppressWarnings("HiddenField")
public TriggerNameBuilder withType(TriggerType type) {
this.type = type;
return this;
}
public String build() {
String preResult = String.format(TEMPLATE, TRIGGER_TYPES_TO_NAME_PARTS.get(type),
schema, tableName, schema, name);
return limitName(preResult);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy