mirror of https://github.com/apache/nifi.git
NIFI-3742: Fixed handling of nulls in DELETE for PutDatabaseRecord
This closes #1709. Signed-off-by: Koji Kawamura <ijokarumawak@apache.org>
This commit is contained in:
parent
8651d79778
commit
d61f519326
|
@ -646,12 +646,24 @@ public class PutDatabaseRecord extends AbstractSessionFactoryProcessor {
|
||||||
if (values != null) {
|
if (values != null) {
|
||||||
if (fieldIndexes != null) {
|
if (fieldIndexes != null) {
|
||||||
for (int i = 0; i < fieldIndexes.size(); i++) {
|
for (int i = 0; i < fieldIndexes.size(); i++) {
|
||||||
ps.setObject(i + 1, values[fieldIndexes.get(i)]);
|
// If DELETE type, insert the object twice because of the null check (see generateDelete for details)
|
||||||
|
if (DELETE_TYPE.equalsIgnoreCase(statementType)) {
|
||||||
|
ps.setObject(i * 2 + 1, values[fieldIndexes.get(i)]);
|
||||||
|
ps.setObject(i * 2 + 2, values[fieldIndexes.get(i)]);
|
||||||
|
} else {
|
||||||
|
ps.setObject(i + 1, values[fieldIndexes.get(i)]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If there's no index map, assume all values are included and set them in order
|
// If there's no index map, assume all values are included and set them in order
|
||||||
for (int i = 0; i < values.length; i++) {
|
for (int i = 0; i < values.length; i++) {
|
||||||
ps.setObject(i + 1, values[i]);
|
// If DELETE type, insert the object twice because of the null check (see generateDelete for details)
|
||||||
|
if (DELETE_TYPE.equalsIgnoreCase(statementType)) {
|
||||||
|
ps.setObject(i * 2 + 1, values[i]);
|
||||||
|
ps.setObject(i * 2 + 2, values[i]);
|
||||||
|
} else {
|
||||||
|
ps.setObject(i + 1, values[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ps.addBatch();
|
ps.addBatch();
|
||||||
|
@ -935,14 +947,20 @@ public class PutDatabaseRecord extends AbstractSessionFactoryProcessor {
|
||||||
sqlBuilder.append(" AND ");
|
sqlBuilder.append(" AND ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String columnName;
|
||||||
if (settings.escapeColumnNames) {
|
if (settings.escapeColumnNames) {
|
||||||
sqlBuilder.append(tableSchema.getQuotedIdentifierString())
|
columnName = tableSchema.getQuotedIdentifierString() + desc.getColumnName() + tableSchema.getQuotedIdentifierString();
|
||||||
.append(desc.getColumnName())
|
|
||||||
.append(tableSchema.getQuotedIdentifierString());
|
|
||||||
} else {
|
} else {
|
||||||
sqlBuilder.append(desc.getColumnName());
|
columnName = desc.getColumnName();
|
||||||
}
|
}
|
||||||
sqlBuilder.append(" = ?");
|
// Need to build a null-safe construct for the WHERE clause, since we are using PreparedStatement and won't know if the values are null. If they are null,
|
||||||
|
// then the filter should be "column IS null" vs "column = null". Since we don't know whether the value is null, we can use the following construct (from NIFI-3742):
|
||||||
|
// (column = ? OR (column is null AND ? is null))
|
||||||
|
sqlBuilder.append("(");
|
||||||
|
sqlBuilder.append(columnName);
|
||||||
|
sqlBuilder.append(" = ? OR (");
|
||||||
|
sqlBuilder.append(columnName);
|
||||||
|
sqlBuilder.append(" is null AND ? is null))");
|
||||||
includedColumns.add(i);
|
includedColumns.add(i);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,7 +144,7 @@ class TestPutDatabaseRecord {
|
||||||
assertEquals('INSERT INTO PERSONS (id, name, code) VALUES (?,?,?)',
|
assertEquals('INSERT INTO PERSONS (id, name, code) VALUES (?,?,?)',
|
||||||
generateInsert(schema, 'PERSONS', tableSchema, settings).sql)
|
generateInsert(schema, 'PERSONS', tableSchema, settings).sql)
|
||||||
|
|
||||||
assertEquals('DELETE FROM PERSONS WHERE id = ? AND name = ? AND code = ?',
|
assertEquals('DELETE FROM PERSONS WHERE (id = ? OR (id is null AND ? is null)) AND (name = ? OR (name is null AND ? is null)) AND (code = ? OR (code is null AND ? is null))',
|
||||||
generateDelete(schema, 'PERSONS', tableSchema, settings).sql)
|
generateDelete(schema, 'PERSONS', tableSchema, settings).sql)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -608,6 +608,51 @@ class TestPutDatabaseRecord {
|
||||||
conn.close()
|
conn.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDeleteWithNulls() throws InitializationException, ProcessException, SQLException, IOException {
|
||||||
|
recreateTable("PERSONS", createPersons)
|
||||||
|
Connection conn = dbcp.getConnection()
|
||||||
|
Statement stmt = conn.createStatement()
|
||||||
|
stmt.execute("INSERT INTO PERSONS VALUES (1,'rec1', 101)")
|
||||||
|
stmt.execute("INSERT INTO PERSONS VALUES (2,'rec2', null)")
|
||||||
|
stmt.execute("INSERT INTO PERSONS VALUES (3,'rec3', 103)")
|
||||||
|
stmt.close()
|
||||||
|
|
||||||
|
final MockRecordParser parser = new MockRecordParser()
|
||||||
|
runner.addControllerService("parser", parser)
|
||||||
|
runner.enableControllerService(parser)
|
||||||
|
|
||||||
|
parser.addSchemaField("id", RecordFieldType.INT)
|
||||||
|
parser.addSchemaField("name", RecordFieldType.STRING)
|
||||||
|
parser.addSchemaField("code", RecordFieldType.INT)
|
||||||
|
|
||||||
|
parser.addRecord(2, 'rec2', null)
|
||||||
|
|
||||||
|
runner.setProperty(PutDatabaseRecord.RECORD_READER_FACTORY, 'parser')
|
||||||
|
runner.setProperty(PutDatabaseRecord.STATEMENT_TYPE, PutDatabaseRecord.DELETE_TYPE)
|
||||||
|
runner.setProperty(PutDatabaseRecord.TABLE_NAME, 'PERSONS')
|
||||||
|
|
||||||
|
runner.enqueue(new byte[0])
|
||||||
|
runner.run()
|
||||||
|
|
||||||
|
runner.assertTransferCount(PutDatabaseRecord.REL_SUCCESS, 1)
|
||||||
|
stmt = conn.createStatement()
|
||||||
|
final ResultSet rs = stmt.executeQuery('SELECT * FROM PERSONS')
|
||||||
|
assertTrue(rs.next())
|
||||||
|
assertEquals(1, rs.getInt(1))
|
||||||
|
assertEquals('rec1', rs.getString(2))
|
||||||
|
assertEquals(101, rs.getInt(3))
|
||||||
|
assertTrue(rs.next())
|
||||||
|
assertEquals(3, rs.getInt(1))
|
||||||
|
assertEquals('rec3', rs.getString(2))
|
||||||
|
assertEquals(103, rs.getInt(3))
|
||||||
|
assertFalse(rs.next())
|
||||||
|
|
||||||
|
stmt.close()
|
||||||
|
conn.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void recreateTable(String tableName, String createSQL) throws ProcessException, SQLException {
|
private void recreateTable(String tableName, String createSQL) throws ProcessException, SQLException {
|
||||||
final Connection conn = dbcp.getConnection()
|
final Connection conn = dbcp.getConnection()
|
||||||
final Statement stmt = conn.createStatement()
|
final Statement stmt = conn.createStatement()
|
||||||
|
|
Loading…
Reference in New Issue