Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 22 additions & 17 deletions herddb-core/src/main/java/herddb/core/TableManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

package herddb.core;

import static herddb.sql.JSQLParserPlanner.delimit;
import static java.util.concurrent.TimeUnit.SECONDS;
import herddb.codec.RecordSerializer;
import herddb.core.PageSet.DataPageMetaData;
Expand Down Expand Up @@ -1262,15 +1263,15 @@ private void executeForeignKeyConstraintsAsParentTable(Table childTable, DataAcc
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(childTable.tablespace);
q.append(delimit(childTable.tablespace));
q.append(".");
q.append(childTable.name);
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(fk.columns[i]);
q.append(delimit(fk.columns[i]));
q.append("=?");
}
return q.toString();
Expand All @@ -1282,39 +1283,39 @@ private void executeForeignKeyConstraintsAsParentTable(Table childTable, DataAcc
} 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(childTable.tablespace);
q.append(delimit(childTable.tablespace));
q.append(".");
q.append(childTable.name);
q.append(delimit(childTable.name));
q.append(" SET ");
for (int i = 0; i < fk.columns.length; i++) {
if (i > 0) {
q.append(",");
}
q.append(fk.columns[i]);
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(fk.columns[i]);
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(childTable.tablespace);
q.append(delimit(childTable.tablespace));
q.append(".");
q.append(childTable.name);
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(fk.columns[i]);
q.append(delimit(fk.columns[i]));
q.append("=?");
}
return q.toString();
Expand Down Expand Up @@ -1363,20 +1364,24 @@ private void executeForeignKeyConstraintsAsParentTable(Table childTable, DataAcc
}

private void validateForeignKeyConsistency(ForeignKeyDef fk, StatementEvaluationContext context, Transaction transaction) throws StatementExecutionException {
if (!tableSpaceManager.getDbmanager().isFullSQLSupportEnabled()) {
// we cannot perform this validation without Calcite
return;
}
Table parentTable = tableSpaceManager.getTableManagerByUUID(fk.parentTableId).getTable();
StringBuilder query = new StringBuilder("SELECT * "
+ " FROM " + this.tableSpaceManager.getTableSpaceName() + "." + this.table.name + " childtable "
+ " FROM " + delimit(this.tableSpaceManager.getTableSpaceName()) + "." + delimit(this.table.name) + " childtable "
+ " WHERE NOT EXISTS (SELECT * "
+ " FROM " + this.tableSpaceManager.getTableSpaceName() + "." + parentTable.name + " parenttable "
+ " FROM " + delimit(this.tableSpaceManager.getTableSpaceName()) + "." + delimit(parentTable.name) + " parenttable "
+ " WHERE ");
for (int i = 0; i < fk.columns.length; i++) {
if (i > 0) {
query.append(" AND ");
}
query.append("childtable.")
.append(fk.columns[i])
.append(delimit(fk.columns[i]))
.append(" = parenttable.")
.append(fk.parentTableColumns[i]);
.append(delimit(fk.parentTableColumns[i]));
}
query.append(")");
TransactionContext tx = transaction != null ? new TransactionContext(transaction.transactionId) : TransactionContext.NO_TRANSACTION;
Expand Down Expand Up @@ -1409,15 +1414,15 @@ private void checkForeignKeyConstraintsAsChildTable(ForeignKeyDef fk, DataAccess
Table parentTable = tableSpaceManager.getTableManagerByUUID(fk.parentTableId).getTable();
// with '*' we are not going to perform projections or copies
StringBuilder q = new StringBuilder("SELECT * FROM ");
q.append(parentTable.tablespace);
q.append(delimit(parentTable.tablespace));
q.append(".");
q.append(parentTable.name);
q.append(delimit(parentTable.name));
q.append(" WHERE ");
for (int i = 0; i < fk.parentTableColumns.length; i++) {
if (i > 0) {
q.append(" AND ");
}
q.append(fk.parentTableColumns[i]);
q.append(delimit(fk.parentTableColumns[i]));
q.append("=?");
}
return q.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,45 @@ protected Iterable<Record> buildVirtualRecordList(Transaction transaction) {
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", "importedNoAction",
"on_update_action", "importedNoAction",
"on_delete_action", on_delete_action,
"on_update_action", on_update_action,
"ordinal_position", (i + 1),
"deferred", "importedKeyNotDeferrable"
));
Expand Down
14 changes: 14 additions & 0 deletions herddb-core/src/main/java/herddb/sql/JSQLParserPlanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ public class JSQLParserPlanner extends AbstractSQLPlanner {

public static final String TABLE_CONSISTENCY_COMMAND = "tableconsistencycheck";
public static final String TABLESPACE_CONSISTENCY_COMMAND = "tablespaceconsistencycheck";

public static String delimit(String name) {
if (name == null) {
return null;
}
return "`" + name + "`";
}
private final PlansCache cache;
/**
* Used in case of unsupported Statement
Expand Down Expand Up @@ -312,6 +319,10 @@ public TranslatedQuery translate(
}

query = rewriteExecuteSyntax(query);
if (query.startsWith("ALTER TABLE") && query.contains("ADD FOREIGN KEY")) {
// jsqlparser does not support unnamed foreign keys in "ALTER TABLE"
query = query.replace("ADD FOREIGN KEY", "ADD CONSTRAINT generate_unnamed FOREIGN KEY");
}
if (query.startsWith("EXPLAIN ")) {
query = query.substring("EXPLAIN ".length());
net.sf.jsqlparser.statement.Statement stmt = parseStatement(query);
Expand Down Expand Up @@ -601,6 +612,9 @@ private Statement buildCreateTableStatement(String defaultTableSpace, CreateTabl

private ForeignKeyDef parseForeignKeyIndex(ForeignKeyIndex fk, Table table, String tableName, String tableSpace) throws StatementExecutionException {
String indexName = fixMySqlBackTicks(fk.getName().toLowerCase());
if (indexName.equals("generate_unnamed")) {
indexName = "fk_" + tableName + "_" + System.nanoTime();
}
int onUpdateCascadeAction = parseForeignKeyAction(fk.getOnUpdateReferenceOption());
int onDeleteCascadeAction = parseForeignKeyAction(fk.getOnDeleteReferenceOption());
Table parentTableSchema = getTable(table.tablespace, fk.getTable());
Expand Down
27 changes: 27 additions & 0 deletions herddb-core/src/test/java/herddb/sql/ForeignKeySQLTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -480,4 +480,31 @@ public void createTableWithOnUpdateSetNull() throws Exception {
}
}

@Test
public void alterAddUnnamedForeignKey() throws Exception {
String nodeId = "localhost";
try (DBManager manager = new DBManager("localhost", new MemoryMetadataStorageManager(), new MemoryDataStorageManager(), new MemoryCommitLogManager(), null, null)) {
manager.start();
CreateTableSpaceStatement st1 = new CreateTableSpaceStatement("tblspace1", Collections.singleton(nodeId), nodeId, 1, 0, 0);
manager.executeStatement(st1, StatementEvaluationContext.DEFAULT_EVALUATION_CONTEXT(), TransactionContext.NO_TRANSACTION);
manager.waitForTablespace("tblspace1", 10000);

execute(manager, "CREATE TABLE tblspace1.parent (k1 string primary key,n1 int,s1 string)", Collections.emptyList());
execute(manager, "CREATE TABLE tblspace1.child (k2 string primary key,n2 int,"
+ "s2 string)", Collections.emptyList());
execute(manager, "ALTER TABLE tblspace1.child ADD FOREIGN KEY (s2,n2) REFERENCES parent(k1,n1)", Collections.emptyList());
Table parentTable = manager.getTableSpaceManager("tblspace1").getTableManager("parent").getTable();
Table childTable = manager.getTableSpaceManager("tblspace1").getTableManager("child").getTable();
assertEquals(1, childTable.foreignKeys.length);
assertEquals(ForeignKeyDef.ACTION_NO_ACTION, childTable.foreignKeys[0].onUpdateAction);
assertEquals(ForeignKeyDef.ACTION_NO_ACTION, childTable.foreignKeys[0].onDeleteAction);
assertEquals(parentTable.uuid, childTable.foreignKeys[0].parentTableId);
assertArrayEquals(new String[]{"s2", "n2"}, childTable.foreignKeys[0].columns);
assertArrayEquals(new String[]{"k1", "n1"}, childTable.foreignKeys[0].parentTableColumns);

// test FK is working
testChildSideOfForeignKey(manager, TransactionContext.NOTRANSACTION_ID, childTable.foreignKeys[0].name);
}
}

}
36 changes: 33 additions & 3 deletions herddb-jdbc/src/main/java/herddb/jdbc/HerdDBDatabaseMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -1237,12 +1237,12 @@ private ResultSet getForeignKeysQueryResults(String query, String schema, String
data.put("FKCOLUMN_NAME", parent_table_column_name);

data.put("KEY_SEQ", ordinal_position);
data.put("UPDATE_RULE", on_update_action);
data.put("DELETE_RULE", on_delete_action);
data.put("UPDATE_RULE", convertFkActions(on_update_action));
data.put("DELETE_RULE", convertFkActions(on_delete_action));

data.put("FK_NAME", child_table_cons_name);
data.put("PK_NAME", null);
data.put("DEFERRABILITY", deferred);
data.put("DEFERRABILITY", convertDeferrability(deferred));
results.add(data);

}
Expand Down Expand Up @@ -1757,4 +1757,34 @@ public boolean isWrapperFor(Class<?> iface) throws SQLException {
return iface.isInstance(this);
}

public static int convertFkActions(String action) {
switch (action + "") {
case "importedNoAction":
return importedKeyNoAction;
case "importedKeyCascade":
return importedKeyCascade;
case "importedKeySetNull":
return importedKeySetNull;
case "importedKeySetDefault":
return importedKeySetDefault;
case "importedKeyRestrict":
return importedKeyRestrict;
default:
return importedKeyNoAction;
}
}

private static int convertDeferrability(String deferred) {
switch (deferred + "") {
case "importedKeyNotDeferrable":
return importedKeyNotDeferrable;
case "importedKeyInitiallyDeferred":
return importedKeyInitiallyDeferred;
case "importedKeyInitiallyImmediate":
return importedKeyInitiallyImmediate;
default:
return importedKeyNotDeferrable;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void test() throws Exception {
Statement statement = con.createStatement()) {
statement.execute("CREATE TABLE ptable (pkey string primary key, p1 string, p2 string)");
statement.execute("CREATE TABLE ctable (ckey string primary key, c1 string, c2 string,"
+ " CONSTRAINT `fk1` FOREIGN KEY (`c1`,`c2`) REFERENCES ptable(`p1`,`p2`))");
+ " CONSTRAINT `fk1` FOREIGN KEY (`c1`,`c2`) REFERENCES ptable(`p1`,`p2`) ON DELETE CASCADE)");
DatabaseMetaData metaData = con.getMetaData();
try (ResultSet rs = metaData.getImportedKeys(null, null, "CTABLE");) {
verifyForeignKeyResultSet(rs);
Expand Down Expand Up @@ -100,16 +100,16 @@ private void verifyForeignKeyResultSet(final ResultSet importedKeys) throws SQLE

assertEquals("fk1", importedKeys.getString("FK_NAME"));
assertEquals(null, importedKeys.getString("PK_NAME"));
assertEquals("importedKeyNotDeferrable", importedKeys.getString("DEFERRABILITY"));
assertEquals(DatabaseMetaData.importedKeyNotDeferrable, importedKeys.getInt("DEFERRABILITY"));
if (count == 0) {
assertEquals("c1", importedKeys.getString("PKCOLUMN_NAME"));
assertEquals("p1", importedKeys.getString("FKCOLUMN_NAME"));
} else {
assertEquals("c2", importedKeys.getString("PKCOLUMN_NAME"));
assertEquals("p2", importedKeys.getString("FKCOLUMN_NAME"));
}
assertEquals("importedNoAction", importedKeys.getString("UPDATE_RULE"));
assertEquals("importedNoAction", importedKeys.getString("DELETE_RULE"));
assertEquals(DatabaseMetaData.importedKeyNoAction, importedKeys.getInt("UPDATE_RULE"));
assertEquals(DatabaseMetaData.importedKeyCascade, importedKeys.getInt("DELETE_RULE"));
count++;
}
assertEquals(2, count);
Expand Down
18 changes: 18 additions & 0 deletions herddb-thirdparty/openjpa-test/nb-configuration.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-shared-configuration>
<!--
This file contains additional configuration written by modules in the NetBeans IDE.
The configuration is intended to be shared among all the users of project and
therefore it is assumed to be part of version control checkout.
Without this configuration present, some functionality in the IDE may be limited or fail altogether.
-->
<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
<!--
Properties that influence various parts of the IDE, especially code formatting and the like.
You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
That way multiple projects can share the same settings (useful for formatting rules for example).
Any value defined here will override the pom.xml file value but is only applicable to the current project.
-->
<netbeans.compile.on.save>none</netbeans.compile.on.save>
</properties>
</project-shared-configuration>
4 changes: 2 additions & 2 deletions herddb-thirdparty/openjpa-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
</dependency>
<dependency>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa</artifactId>
<version>3.1.1</version>
<artifactId>openjpa-persistence-jdbc</artifactId>
<version>3.1.3-SNAPSHOT</version>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be seen a problematic for a release, but it is needed in order to see the test work.
if we want to cut a release we must revert back to 3.1.2 and comment parts of the test
I hope that with @rmannibucau 's help we could release OpenJPA 3.1.3 soon

</dependency>
<dependency>
<!-- use 'thin' driver, we want OpenJPA users to be able to run tests
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Licensed to Diennea S.r.l. under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Diennea S.r.l. licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package test.entity;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_FIELD", "NP_BOOLEAN_RETURN_NULL"})
public class Address {

@Id
@GeneratedValue
private long id;
private String city;

}
Loading