All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.curs.celesta.score.BasicTable Maven / Gradle / Ivy

The newest version!
package ru.curs.celesta.score;

import org.json.JSONException;
import ru.curs.celesta.CelestaException;

import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * Super class for {@link Table} and {@link ReadOnlyTable} that encompasses common logic
 * for both table types.
 *
 * @author Pavel Perminov ([email protected])
 * @since 2019-07-14
 */
public abstract class BasicTable extends DataGrainElement implements TableElement {

    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 NamedElementHolder> columns = new NamedElementHolder<>() {
        @Override
        protected String getErrorMsg(String name) {
            return String.format("Column '%s' defined more than once in table '%s'.", name, getName());
        }

    };

    private final boolean canHaveEmptyPK;

    private final Set fKeys = new LinkedHashSet<>();

    private final Set indices = new HashSet<>();

    private boolean pkFinalized = false;

    private boolean autoUpdate = true;

    private String pkConstraintName;

    protected BasicTable(GrainPart grainPart, String name, boolean canHaveEmptyPK) throws ParseException {
        super(grainPart, name);
        getGrain().addElement(this);
        this.canHaveEmptyPK = canHaveEmptyPK;
    }

    /**
     * Unmodified list of table columns.
     */
    @Override
    public Map> getColumns() {
        return columns.getElements();
    }


    @Override
    public final Column getColumn(String colName) throws ParseException {
        Column result = columns.get(colName);
        if (result == null) {
            throw new ParseException(
                    String.format("Column '%s' not found in table '%s.%s'", colName, getGrain().getName(), getName()));
        }
        return result;
    }

    @Override
    public final Map> getPrimaryKey() {
        return pk.getElements();
    }

    /**
     * Adds a column to the table.
     *
     * @param column new column
     * @throws ParseException if a column with the same name is already defined
     */
    @Override
    public void addColumn(Column column) throws ParseException {
        if (column.getParentTable() != this) {
            throw new IllegalArgumentException();
        }
        getGrain().modify();
        columns.addElement(column);
    }

    @Override
    public final String toString() {
        return "name: " + getName() + " " + columns.toString();
    }

    /**
     * Sets primary key on the table in a form of array of columns.
     * It is used for dynamic metadata management.
     *
     * @param columnNames array of columns
     * @throws ParseException in case when an empty array is passed in
     */
    public void setPK(String... columnNames) throws ParseException {
        if (columnNames == null || columnNames.length == 0 && !canHaveEmptyPK) {
            throw new ParseException(
                    String.format("Primary key for table %s.%s cannot be empty.", getGrain().getName(), getName()));
        }
        for (String n : columnNames) {
            validatePKColumn(n);
        }
        getGrain().modify();
        pk.clear();
        pkFinalized = false;
        for (String n : columnNames) {
            addPK(n);
        }
        finalizePK();
    }

    /**
     * Adds a column of the primary key.
     *
     * @param name primary key column name
     */
    void addPK(String name) throws ParseException {
        name = getGrain().getScore().getIdentifierParser().parse(name);
        if (pkFinalized) {
            throw new ParseException(String.format("More than one PRIMARY KEY definition in table '%s'.", getName()));
        }
        Column c = validatePKColumn(name);
        pk.addElement(c);
    }

    private Column validatePKColumn(String name) throws ParseException {
        if (VersionedElement.REC_VERSION.equals(name)) {
            throw new ParseException(String.format("Column '%s' is not allowed for primary key.", name));
        }
        Column c = columns.get(name);
        if (c == null) {
            throw new ParseException(String.format("Column '%s' is not defined in table '%s'.", name, getName()));
        }
        if (c.isNullable()) {
            throw new ParseException(String.format(
                    "Column '%s' is nullable and therefore it cannot be " + "a part of a primary key in table '%s'.",
                    name, getName()));
        }
        if (c instanceof BinaryColumn) {
            throw new ParseException(String.format("Column %s is of long binary type and therefore "
                    + "it cannot a part of a primary key in table '%s'.", name, getName()));
        }
        if (c instanceof StringColumn && ((StringColumn) c).isMax()) {
            throw new ParseException(String.format(
                    "Column '%s' is of TEXT type and therefore " + "it cannot a part of a primary key in table '%s'.",
                    name, getName()));
        }

        return c;
    }

    final void addFK(ForeignKey fk) throws ParseException {
        if (fk.getParentTable() != this) {
            throw new IllegalArgumentException();
        }
        if (fKeys.contains(fk)) {
            StringBuilder sb = new StringBuilder();
            for (Column c : fk.getColumns().values()) {
                if (sb.length() != 0) {
                    sb.append(", ");
                }
                sb.append(c.getName());
            }
            throw new ParseException(String.format("Foreign key with columns %s is already defined in table '%s'",
                    sb, getName()));
        }
        getGrain().modify();
        fKeys.add(fk);
    }

    final synchronized void removeFK(ForeignKey foreignKey) throws ParseException {
        getGrain().modify();
        fKeys.remove(foreignKey);
    }

    @Override
    public final 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()));
            }
        }
        // It's not allowed to delete compound part of a foreign key
        for (ForeignKey fk : fKeys) {
            if (fk.getColumns().containsValue(column)) {
                throw new ParseException(String.format(
                        YOU_CANNOT_DROP_A_COLUMN_THAT_BELONGS_TO
                                + "a foreign key. Drop or change relevant foreign key first.",
                        getGrain().getName(), getName(), column.getName()));
            }
        }
        getGrain().modify();
        columns.remove(column);
    }

    /**
     * Finalizes the creation of the primary key.
     *
     * @throws ParseException if the primary key is empty.
     */
    public void finalizePK() throws ParseException {
        if (pk.isEmpty() && !canHaveEmptyPK) {
            throw new ParseException(String.format("No primary key defined for table %s!", getName()));
        }
        pkFinalized = true;
    }

    /**
     * Returns a set of foreign keys for the table.
     *
     * @return
     */
    public Set getForeignKeys() {
        return Collections.unmodifiableSet(fKeys);
    }

    /**
     * Returns a set of indices for the table.
     *
     * @return
     */
    public Set getIndices() {
        return Collections.unmodifiableSet(indices);
    }

    @Override
    public final String getPkConstraintName() {
        return pkConstraintName == null ? limitName("pk_" + getName()) : pkConstraintName;
    }

    /**
     * Sets the name of constraint for the primary key.
     *
     * @param pkConstraintName PK constraint name
     * @throws ParseException incorrect name
     */
    public void setPkConstraintName(String pkConstraintName) throws ParseException {
        if (pkConstraintName != null) {
            pkConstraintName = getGrain().getScore().getIdentifierParser().parse(pkConstraintName);
        }
        this.pkConstraintName = pkConstraintName;
    }

    /**
     * Whether autoupdate is on/off.
*
* {@code false} value indicates that the table was created with the option * WITH NO STRUCTURE UPDATE and it won't take part in the DB autoupdate. * Default is {@code true}. * * @return */ public boolean isAutoUpdate() { return autoUpdate; } /** * Sets or clears the option WITH NO STRUCTURE UPDATE. * * @param autoUpdate {@code true} if the table is updated automatically, * {@code false} - in the opposite case. */ public void setAutoUpdate(boolean autoUpdate) { this.autoUpdate = autoUpdate; } @Override public final int getColumnIndex(String name) { return columns.getIndex(name); } final void addIndex(Index index) { indices.add(index); } final void removeIndex(Index index) { indices.remove(index); } /** * Returns true in case there are materialized views built on top of this table. */ public boolean hasMaterializedViews() { return getGrain().getElements(MaterializedView.class).values().stream() .anyMatch(mv -> mv.getRefTable().getTable().equals(this)); } /** * Returns the integer column with default value provided by a sequence * (or empty Optional if such column does not exists). */ public Optional getAutoincrementedColumn() { return getColumns().values().stream() .filter(IntegerColumn.class::isInstance) .map(IntegerColumn.class::cast) .filter(ic -> ic.getSequence() != null) .findFirst(); } /** * Returns interfaces that are implemented by the cursor (values of * 'implements' property) for this table. * * @return */ public List getImplements() { try { return CelestaDocUtils.getList(getCelestaDoc(), CelestaDocUtils.IMPLEMENTS); } catch (JSONException e1) { throw new CelestaException("Error in CelestaDoc for %s.%s: %s", getGrain().getName(), getName(), e1.getMessage()); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy