use of herddb.model.ForeignKeyDef in project herddb by diennea.
the class JSQLParserPlanner method buildCreateTableStatement.
private Statement buildCreateTableStatement(String defaultTableSpace, CreateTable s) throws StatementExecutionException {
String tableSpace = fixMySqlBackTicks(s.getTable().getSchemaName());
String tableName = fixMySqlBackTicks(s.getTable().getName());
if (tableSpace == null) {
tableSpace = defaultTableSpace;
}
if (s.getColumnDefinitions() == null) {
throw new StatementExecutionException("A table must have at least 1 column");
}
final boolean isNotExsists = s.isIfNotExists();
try {
boolean foundPk = false;
Table.Builder tablebuilder = Table.builder().uuid(UUID.randomUUID().toString()).name(tableName).tablespace(tableSpace);
Set<String> primaryKey = new HashSet<>();
Set<String> simpleUniqueFields = new HashSet<>();
if (s.getIndexes() != null) {
for (Index index : s.getIndexes()) {
if (index.getType().equalsIgnoreCase("PRIMARY KEY")) {
for (String n : index.getColumnsNames()) {
n = fixMySqlBackTicks(n.toLowerCase());
tablebuilder.primaryKey(n);
primaryKey.add(n);
foundPk = true;
}
}
}
}
int position = 0;
for (ColumnDefinition cf : s.getColumnDefinitions()) {
String columnName = fixMySqlBackTicks(cf.getColumnName().toLowerCase());
int type;
String dataType = cf.getColDataType().getDataType();
List<String> columnSpecs = decodeColumnSpecs(cf.getColumnSpecs());
type = sqlDataTypeToColumnType(dataType, cf.getColDataType().getArgumentsStringList(), columnSpecs);
Bytes defaultValue = decodeDefaultValue(cf, type);
if (!columnSpecs.isEmpty()) {
boolean auto_increment = decodeAutoIncrement(columnSpecs);
if (columnSpecs.contains("PRIMARY")) {
foundPk = true;
tablebuilder.primaryKey(columnName, auto_increment);
}
if (auto_increment && primaryKey.contains(columnName)) {
tablebuilder.primaryKey(columnName, auto_increment);
}
boolean isUnique = columnSpecs.contains("UNIQUE");
if (isUnique) {
simpleUniqueFields.add(columnName);
}
}
tablebuilder.column(columnName, type, position++, defaultValue);
}
if (!foundPk) {
tablebuilder.column("_pk", ColumnTypes.LONG, position++, null);
tablebuilder.primaryKey("_pk", true);
}
Table table = tablebuilder.build();
List<herddb.model.Index> otherIndexes = new ArrayList<>();
List<herddb.model.ForeignKeyDef> foreignKeys = new ArrayList<>();
if (s.getIndexes() != null) {
for (Index index : s.getIndexes()) {
if (index.getType().equalsIgnoreCase("PRIMARY KEY")) {
} else if (index.getType().equalsIgnoreCase("INDEX") || index.getType().equalsIgnoreCase("KEY") || index.getType().equalsIgnoreCase("UNIQUE KEY")) {
String indexName = fixMySqlBackTicks(index.getName().toLowerCase());
String indexType = convertIndexType(null);
boolean unique = index.getType().equalsIgnoreCase("UNIQUE KEY");
herddb.model.Index.Builder builder = herddb.model.Index.builder().onTable(table).name(indexName).unique(unique).type(indexType).uuid(UUID.randomUUID().toString());
for (String columnName : index.getColumnsNames()) {
columnName = fixMySqlBackTicks(columnName.toLowerCase());
Column column = table.getColumn(columnName);
if (column == null) {
throw new StatementExecutionException("no such column " + columnName + " on table " + tableName + " in tablespace " + tableSpace);
}
builder.column(column.name, column.type);
}
otherIndexes.add(builder.build());
} else if (index.getType().equals("FOREIGN KEY")) {
ForeignKeyIndex fk = (ForeignKeyIndex) index;
ForeignKeyDef fkDef = parseForeignKeyIndex(fk, table, tableName, tableSpace);
foreignKeys.add(fkDef);
} else {
throw new StatementExecutionException("Unsupported index type " + index.getType());
}
}
}
for (String col : simpleUniqueFields) {
herddb.model.Index.Builder builder = herddb.model.Index.builder().onTable(table).name(table.name + "_unique_" + col).unique(true).type(herddb.model.Index.TYPE_BRIN).uuid(UUID.randomUUID().toString()).column(col, table.getColumn(col).type);
otherIndexes.add(builder.build());
}
if (!foreignKeys.isEmpty()) {
table = table.withForeignKeys(foreignKeys.toArray(new ForeignKeyDef[0]));
}
CreateTableStatement statement = new CreateTableStatement(table, otherIndexes, isNotExsists);
return statement;
} catch (IllegalArgumentException err) {
throw new StatementExecutionException("bad table definition: " + err.getMessage(), err);
}
}
use of herddb.model.ForeignKeyDef in project herddb by diennea.
the class JSQLParserPlanner method buildAlterStatement.
private Statement buildAlterStatement(String defaultTableSpace, Alter alter) throws StatementExecutionException {
if (alter.getTable() == null) {
throw new StatementExecutionException("missing table name");
}
String tableSpace = alter.getTable().getSchemaName();
if (tableSpace == null) {
tableSpace = defaultTableSpace;
}
tableSpace = fixMySqlBackTicks(tableSpace);
List<Column> addColumns = new ArrayList<>();
List<Column> modifyColumns = new ArrayList<>();
List<String> dropColumns = new ArrayList<>();
List<String> dropForeignKeys = new ArrayList<>();
List<ForeignKeyDef> addForeignKeys = new ArrayList<>();
String tableName = fixMySqlBackTicks(alter.getTable().getName().toLowerCase());
if (alter.getAlterExpressions() == null || alter.getAlterExpressions().size() != 1) {
throw new StatementExecutionException("supported multi-alter operation '" + alter + "'");
}
AlterExpression alterExpression = alter.getAlterExpressions().get(0);
AlterOperation operation = alterExpression.getOperation();
Boolean changeAutoIncrement = null;
TableSpaceManager tableSpaceManager = manager.getTableSpaceManager(tableSpace);
if (tableSpaceManager == null) {
throw new StatementExecutionException("bad tablespace '" + tableSpace + "'");
}
Table table = getTable(defaultTableSpace, alter.getTable());
switch(operation) {
case ADD:
{
if (alterExpression.getColDataTypeList() != null) {
List<AlterExpression.ColumnDataType> cols = alterExpression.getColDataTypeList();
for (AlterExpression.ColumnDataType cl : cols) {
List<String> columnSpecs = decodeColumnSpecs(cl.getColumnSpecs());
int type = sqlDataTypeToColumnType(cl.getColDataType().getDataType(), cl.getColDataType().getArgumentsStringList(), columnSpecs);
Column newColumn = Column.column(fixMySqlBackTicks(cl.getColumnName()), type, decodeDefaultValue(cl, type));
addColumns.add(newColumn);
}
} else if (alterExpression.getIndex() != null && alterExpression.getIndex() instanceof ForeignKeyIndex) {
ForeignKeyDef fkIndex = parseForeignKeyIndex((ForeignKeyIndex) alterExpression.getIndex(), table, tableName, tableSpace);
addForeignKeys.add(fkIndex);
} else {
throw new StatementExecutionException("Unrecognized ALTER TABLE ADD ... statement");
}
}
break;
case DROP:
if (alterExpression.getColumnName() != null) {
dropColumns.add(fixMySqlBackTicks(alterExpression.getColumnName()));
} else if (alterExpression.getConstraintName() != null) {
dropForeignKeys.add(fixMySqlBackTicks(alterExpression.getConstraintName()));
} else {
throw new StatementExecutionException("Unrecognized ALTER TABLE DROP ... statement");
}
break;
case MODIFY:
{
List<AlterExpression.ColumnDataType> cols = alterExpression.getColDataTypeList();
for (AlterExpression.ColumnDataType cl : cols) {
String columnName = fixMySqlBackTicks(cl.getColumnName().toLowerCase());
Column oldColumn = table.getColumn(columnName);
if (oldColumn == null) {
throw new StatementExecutionException("bad column " + columnName + " in table " + tableName + " in tablespace '" + tableSpace + "'");
}
Map<String, AbstractIndexManager> indexes = tableSpaceManager.getIndexesOnTable(tableName);
if (indexes != null) {
for (AbstractIndexManager am : indexes.values()) {
for (String indexedColumn : am.getColumnNames()) {
indexedColumn = fixMySqlBackTicks(indexedColumn);
if (indexedColumn.equalsIgnoreCase(oldColumn.name)) {
throw new StatementExecutionException("cannot alter indexed " + columnName + " in table " + tableName + " in tablespace '" + tableSpace + "'," + "index name is " + am.getIndexName());
}
}
}
}
List<String> columnSpecs = decodeColumnSpecs(cl.getColumnSpecs());
int newType = sqlDataTypeToColumnType(cl.getColDataType().getDataType(), cl.getColDataType().getArgumentsStringList(), columnSpecs);
if (oldColumn.type != newType) {
if (ColumnTypes.isNotNullToNullConversion(oldColumn.type, newType)) {
// allow change from "STRING NOT NULL" to "STRING NULL"
} else if (ColumnTypes.isNullToNotNullConversion(oldColumn.type, newType)) {
// allow change from "STRING NULL" to "STRING NOT NULL"
// it will require a check on table at execution time
} else {
throw new StatementExecutionException("cannot change datatype to " + ColumnTypes.typeToString(newType) + " for column " + columnName + " (" + ColumnTypes.typeToString(oldColumn.type) + ") in table " + tableName + " in tablespace '" + tableSpace + "'");
}
}
if (table.isPrimaryKeyColumn(columnName)) {
boolean new_auto_increment = decodeAutoIncrement(columnSpecs);
if (new_auto_increment && table.primaryKey.length > 1) {
throw new StatementExecutionException("cannot add auto_increment flag to " + cl.getColDataType().getDataType() + " for column " + columnName + " in table " + tableName + " in tablespace '" + tableSpace + "'");
}
if (table.auto_increment != new_auto_increment) {
changeAutoIncrement = new_auto_increment;
}
}
Bytes newDefault = oldColumn.defaultValue;
if (containsDefaultClause(cl)) {
newDefault = decodeDefaultValue(cl, newType);
}
Column newColumnDef = Column.column(columnName, newType, oldColumn.serialPosition, newDefault);
modifyColumns.add(newColumnDef);
}
}
break;
case CHANGE:
{
String columnName = alterExpression.getColOldName();
List<AlterExpression.ColumnDataType> cols = alterExpression.getColDataTypeList();
if (cols.size() != 1) {
throw new StatementExecutionException("bad CHANGE column " + columnName + " in table " + tableName + " in tablespace '" + tableSpace + "'");
}
AlterExpression.ColumnDataType cl = cols.get(0);
Column oldColumn = table.getColumn(columnName);
if (oldColumn == null) {
throw new StatementExecutionException("bad column " + columnName + " in table " + tableName + " in tablespace '" + tableSpace + "'");
}
Map<String, AbstractIndexManager> indexes = tableSpaceManager.getIndexesOnTable(tableName);
if (indexes != null) {
for (AbstractIndexManager am : indexes.values()) {
for (String indexedColumn : am.getColumnNames()) {
indexedColumn = fixMySqlBackTicks(indexedColumn);
if (indexedColumn.equalsIgnoreCase(oldColumn.name)) {
throw new StatementExecutionException("cannot alter indexed " + columnName + " in table " + tableName + " in tablespace '" + tableSpace + "'," + "index name is " + am.getIndexName());
}
}
}
}
List<String> columnSpecs = decodeColumnSpecs(cl.getColumnSpecs());
int newType = sqlDataTypeToColumnType(cl.getColDataType().getDataType(), cl.getColDataType().getArgumentsStringList(), columnSpecs);
if (oldColumn.type != newType) {
throw new StatementExecutionException("cannot change datatype to " + ColumnTypes.typeToString(newType) + " for column " + columnName + " (" + ColumnTypes.typeToString(oldColumn.type) + ") in table " + tableName + " in tablespace '" + tableSpace + "'");
}
if (table.isPrimaryKeyColumn(columnName)) {
boolean new_auto_increment = decodeAutoIncrement(columnSpecs);
if (new_auto_increment && table.primaryKey.length > 1) {
throw new StatementExecutionException("cannot add auto_increment flag to " + cl.getColDataType().getDataType() + " for column " + columnName + " in table " + tableName + " in tablespace '" + tableSpace + "'");
}
if (table.auto_increment != new_auto_increment) {
changeAutoIncrement = new_auto_increment;
}
}
String renameTo = fixMySqlBackTicks(cl.getColumnName().toLowerCase());
if (renameTo != null) {
columnName = renameTo;
}
Column newColumnDef = Column.column(columnName, newType, oldColumn.serialPosition, oldColumn.defaultValue);
modifyColumns.add(newColumnDef);
}
break;
default:
throw new StatementExecutionException("supported alter operation '" + alter + "'");
}
return new AlterTableStatement(addColumns, modifyColumns, dropColumns, changeAutoIncrement, tableName.toLowerCase(), tableSpace, null, dropForeignKeys, addForeignKeys);
}
use of herddb.model.ForeignKeyDef in project herddb by diennea.
the class SysforeignkeysTableManager method buildVirtualRecordList.
@Override
protected Iterable<Record> buildVirtualRecordList(Transaction transaction) {
List<Table> tables = tableSpaceManager.getAllVisibleTables(transaction);
List<Record> result = new ArrayList<>();
for (Table child : tables) {
if (child.foreignKeys == null) {
continue;
}
String child_table_name = child.name;
for (ForeignKeyDef fk : child.foreignKeys) {
Table parent = tables.stream().filter(ta -> ta.uuid.equals(fk.parentTableId)).findAny().orElse(null);
if (parent == null) {
continue;
}
for (int i = 0; i < fk.columns.length; i++) {
String child_column_name = fk.columns[i];
String parent_column_name = fk.parentTableColumns[i];
String parent_table_name = parent.name;
String on_delete_action;
switch(fk.onDeleteAction) {
case ForeignKeyDef.ACTION_CASCADE:
on_delete_action = "importedKeyCascade";
break;
case ForeignKeyDef.ACTION_NO_ACTION:
on_delete_action = "importedNoAction";
break;
case ForeignKeyDef.ACTION_SETNULL:
on_delete_action = "importedKeySetNull";
break;
default:
on_delete_action = "importedKeyCascade";
break;
}
String on_update_action;
switch(fk.onUpdateAction) {
case ForeignKeyDef.ACTION_CASCADE:
on_update_action = "importedKeyCascade";
break;
case ForeignKeyDef.ACTION_NO_ACTION:
on_update_action = "importedNoAction";
break;
case ForeignKeyDef.ACTION_SETNULL:
on_update_action = "importedKeySetNull";
break;
default:
on_update_action = "importedKeyCascade";
break;
}
result.add(RecordSerializer.makeRecord(table, "child_table_name", child_table_name, "child_table_column_name", child_column_name, "child_table_cons_name", fk.name, "parent_table_name", parent_table_name, "parent_table_column_name", parent_column_name, "on_delete_action", on_delete_action, "on_update_action", on_update_action, "ordinal_position", (i + 1), "deferred", "importedKeyNotDeferrable"));
}
}
}
return result;
}
use of herddb.model.ForeignKeyDef in project herddb by diennea.
the class TableManager method validateAlterTable.
@Override
public void validateAlterTable(Table table, StatementEvaluationContext context) throws StatementExecutionException {
List<String> columnsChangedFromNullToNotNull = new ArrayList<>();
for (Column c : this.table.columns) {
Column newColumnSpecs = table.getColumn(c.name);
if (newColumnSpecs == null) {
// dropped column
LOGGER.log(Level.INFO, "Table {0}.{1} dropping column {2}", new Object[] { table.tablespace, table.name, c.name });
} else if (newColumnSpecs.type == c.type) {
// no data type change
} else if (ColumnTypes.isNotNullToNullConversion(c.type, newColumnSpecs.type)) {
LOGGER.log(Level.INFO, "Table {0}.{1} making column {2} NULLABLE", new Object[] { table.tablespace, table.name, newColumnSpecs.name });
} else if (ColumnTypes.isNullToNotNullConversion(c.type, newColumnSpecs.type)) {
LOGGER.log(Level.INFO, "Table {0}.{1} making column {2} NOT NULL", new Object[] { table.tablespace, table.name, newColumnSpecs.name });
columnsChangedFromNullToNotNull.add(c.name);
}
}
for (final String column : columnsChangedFromNullToNotNull) {
LOGGER.log(Level.INFO, "Table {0}.{1} validating column {2}, check for NULL values", new Object[] { table.tablespace, table.name, column });
ScanStatement scan = new ScanStatement(this.table.tablespace, this.table, new Predicate() {
@Override
public boolean evaluate(Record record, StatementEvaluationContext context) throws StatementExecutionException {
return record.getDataAccessor(table).get(column) == null;
}
});
// fast fail
scan.setLimits(new ScanLimitsImpl(1, 0));
boolean foundOneNull = false;
try (DataScanner scanner = this.scan(scan, context, null, false, false)) {
foundOneNull = scanner.hasNext();
} catch (DataScannerException err) {
throw new StatementExecutionException(err);
}
if (foundOneNull) {
throw new StatementExecutionException("Found a record in table " + table.name + " that contains a NULL value for column " + column + " ALTER command is not possible");
}
}
// if we are adding new FK we have to check that the FK is not violated
if (table.foreignKeys != null) {
List<ForeignKeyDef> newForeignKeys;
if (this.table.foreignKeys == null) {
newForeignKeys = Arrays.asList(table.foreignKeys);
} else {
Set<String> currentKfs = Stream.of(this.table.foreignKeys).map(f -> f.name.toLowerCase()).collect(Collectors.toSet());
newForeignKeys = Stream.of(table.foreignKeys).filter(fk -> !currentKfs.contains(fk.name)).collect(Collectors.toList());
}
for (ForeignKeyDef newFk : newForeignKeys) {
validateForeignKeyConsistency(newFk, context, null);
}
}
}
use of herddb.model.ForeignKeyDef in project herddb by diennea.
the class TableManager method executeForeignKeyConstraintsAsParentTable.
private void executeForeignKeyConstraintsAsParentTable(Table childTable, DataAccessor previousValuesOnParentTable, StatementEvaluationContext context, Transaction transaction, boolean delete) throws StatementExecutionException {
// invalidated consistently during DML operations.
for (ForeignKeyDef fk : childTable.foreignKeys) {
String query = parentForeignKeyQueries.computeIfAbsent(childTable.name + "." + fk.name + ".#" + delete, (l -> {
if (fk.onDeleteAction == ForeignKeyDef.ACTION_CASCADE && delete) {
StringBuilder q = new StringBuilder("DELETE FROM ");
q.append(delimit(childTable.tablespace));
q.append(".");
q.append(delimit(childTable.name));
q.append(" WHERE ");
for (int i = 0; i < fk.columns.length; i++) {
if (i > 0) {
q.append(" AND ");
}
q.append(delimit(fk.columns[i]));
q.append("=?");
}
return q.toString();
} else if (fk.onUpdateAction == ForeignKeyDef.ACTION_CASCADE && !delete) {
// the change is more complex, let's keep it for a future work
throw new StatementExecutionException("No supported ON UPDATE CASCADE");
} else if ((fk.onDeleteAction == ForeignKeyDef.ACTION_SETNULL && delete) || (fk.onUpdateAction == ForeignKeyDef.ACTION_SETNULL && !delete)) {
// delete or update it is the same for SET NULL
StringBuilder q = new StringBuilder("UPDATE ");
q.append(delimit(childTable.tablespace));
q.append(".");
q.append(delimit(childTable.name));
q.append(" SET ");
for (int i = 0; i < fk.columns.length; i++) {
if (i > 0) {
q.append(",");
}
q.append(delimit(fk.columns[i]));
q.append("= NULL ");
}
q.append(" WHERE ");
for (int i = 0; i < fk.columns.length; i++) {
if (i > 0) {
q.append(" AND ");
}
q.append(delimit(fk.columns[i]));
q.append("=?");
}
return q.toString();
} else {
// NO ACTION case, check that there is no matching record in the child table that wouble be invalidated
// with '*' we are not going to perform projections or copies
StringBuilder q = new StringBuilder("SELECT * FROM ");
q.append(delimit(childTable.tablespace));
q.append(".");
q.append(delimit(childTable.name));
q.append(" WHERE ");
for (int i = 0; i < fk.columns.length; i++) {
if (i > 0) {
q.append(" AND ");
}
q.append(delimit(fk.columns[i]));
q.append("=?");
}
return q.toString();
}
}));
final List<Object> valuesToMatch = new ArrayList<>(fk.parentTableColumns.length);
for (int i = 0; i < fk.parentTableColumns.length; i++) {
valuesToMatch.add(previousValuesOnParentTable.get(fk.parentTableColumns[i]));
}
TransactionContext tx = transaction != null ? new TransactionContext(transaction.transactionId) : TransactionContext.NO_TRANSACTION;
if (fk.onDeleteAction == ForeignKeyDef.ACTION_CASCADE && delete || fk.onUpdateAction == ForeignKeyDef.ACTION_CASCADE && !delete || fk.onUpdateAction == ForeignKeyDef.ACTION_SETNULL && !delete || fk.onDeleteAction == ForeignKeyDef.ACTION_SETNULL && delete) {
tableSpaceManager.getDbmanager().executeSimpleStatement(tableSpaceManager.getTableSpaceName(), query, valuesToMatch, // every record
-1, // keep read locks in TransactionContext
true, tx, null);
} else {
boolean fkOk;
try (DataScanner scan = tableSpaceManager.getDbmanager().executeSimpleQuery(tableSpaceManager.getTableSpaceName(), query, valuesToMatch, // only one record
1, // keep read locks in TransactionContext
true, tx, null)) {
List<DataAccessor> resultSet = scan.consume();
// we are on the parent side of the relation
// we are okay if there is no matching record
// TODO: return the list of PKs in order to implement CASCADE operations
fkOk = resultSet.isEmpty();
} catch (DataScannerException err) {
throw new StatementExecutionException(err);
}
if (!fkOk) {
throw new ForeignKeyViolationException(fk.name, "foreignKey " + childTable.name + "." + fk.name + " violated");
}
}
}
}
Aggregations