Skip to content
Permalink
Browse files
IGNITE-16422 Fix ArrayOutOfBoundException on row update via SQL (#606)
  • Loading branch information
vladErmakov07 committed Feb 8, 2022
1 parent 2cf7b4b commit d0eab124696e9d11c2dc5f171921859b3e90b0ba
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 45 deletions.
@@ -18,52 +18,123 @@
package org.apache.ignite.internal.runner.app.jdbc;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.function.Consumer;
import org.apache.ignite.table.KeyValueView;
import org.apache.ignite.table.Tuple;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

/**
* Update statement self test.
*/
@Disabled("https://issues.apache.org/jira/browse/IGNITE-15655")
public class ItJdbcUpdateStatementSelfTest extends ItJdbcAbstractStatementSelfTest {
public class ItJdbcUpdateStatementSelfTest extends AbstractJdbcSelfTest {
/**
* Execute test.
*
* @throws SQLException If failed.
*/
@Test
public void testExecute() throws SQLException {
stmt.execute("update PUBLIC.PERSON set firstName = 'Jack' where substring(SID, 2, 1)::int % 2 = 0");
public void testExecuteUpdateMultipleColumnsUpdate() throws Exception {
final String q1 = "CREATE TABLE usertable (\n"
+ " ycsb_key1 int NOT NULL,\n"
+ " ycsb_key2 varchar(3) NOT NULL,\n"
+ " field1 varchar(100),\n"
+ " field2 varchar(100),\n"
+ " field3 varchar(100),\n"
+ " field4 varchar(100),\n"
+ " field5 varchar(100),\n"
+ " field6 varchar(100),\n"
+ " field7 varchar(100),\n"
+ " field8 varchar(100),\n"
+ " field9 varchar(100),\n"
+ " field10 varchar(100),\n"
+ " PRIMARY KEY(ycsb_key1, ycsb_key2)\n"
+ ");";

KeyValueView<Tuple, Tuple> person = clusterNodes.get(0).tables()
.table("PUBLIC.PERSON").keyValueView();
final String q2 = "INSERT INTO usertable values(1, 'key', 'a1','a2','a3','a4','a5','a6','a7','a8','a9','a10');";

assertEquals("John", person.get(null, Tuple.create().set("ID", 1)).stringValue("FIRSTNAME"));
assertEquals("Jack", person.get(null, Tuple.create().set("ID", 2)).stringValue("FIRSTNAME"));
assertEquals("Mike", person.get(null, Tuple.create().set("ID", 3)).stringValue("FIRSTNAME"));
final String q3 = "UPDATE usertable SET FIELD1='b1',FIELD2='b2',FIELD3='b3',FIELD4='b4',FIELD5='b5',"
+ "FIELD6='b6',FIELD7='b7',FIELD8='b8',FIELD9='b9',FIELD10='b10' WHERE YCSB_KEY1=1";

final String q4 = "DROP TABLE usertable;";

assertEquals(0, stmt.executeUpdate(q1));
assertEquals(1, stmt.executeUpdate(q2));
assertEquals(1, stmt.executeUpdate(q3));

stmt.executeQuery("SELECT * FROM usertable WHERE YCSB_KEY1=1;");

ResultSet resultSet = stmt.getResultSet();

assertTrue(resultSet.next());

for (int i = 1; i < 11; i++) {
assertEquals("b" + i, resultSet.getString(i + 2));
}

assertEquals(0, stmt.executeUpdate(q4));
}

/**
* Execute update test.
* Execute test.
*
* @throws SQLException If failed.
*/
@Test
public void testExecuteUpdate() throws SQLException {
int i = stmt.executeUpdate(
"update PUBLIC.PERSON set firstName = 'Jack' where substring(SID, 2, 1)::int % 2 = 0");
public void testExecuteAndExecuteUpdate() throws SQLException {
testExecute((String updateQuery) -> {
try {
stmt.executeUpdate(updateQuery);
} catch (SQLException e) {
fail(e);
}
});

assertEquals(1, i);
testExecute((String updateQuery) -> {
try {
stmt.execute(updateQuery);
} catch (SQLException e) {
fail(e);
}
});
}

KeyValueView<Tuple, Tuple> person = clusterNodes.get(0).tables()
.table("PUBLIC.PERSON").keyValueView();
private void testExecute(Consumer<String> updateFunction) throws SQLException {
final String createSql = "CREATE TABLE public.person(id INTEGER PRIMARY KEY, sid VARCHAR,"
+ " firstname VARCHAR NOT NULL, lastname VARCHAR NOT NULL, age INTEGER NOT NULL)";

final String insertSql = "INSERT INTO public.person(sid, id, firstname, lastname, age) VALUES "
+ "('p1', 1, 'John', 'White', 25), "
+ "('p2', 2, 'Joe', 'Black', 35), "
+ "('p3', 3, 'Mike', 'Green', 40)";

final String updateSql = "update PUBLIC.PERSON set firstName = 'Jack' where substring(SID, 2, 1)::int % 2 = 0";

final String dropSql = "DROP TABLE PUBLIC.PERSON;";

updateFunction.accept(createSql);
updateFunction.accept(insertSql);
updateFunction.accept(updateSql);

KeyValueView<Integer, Person> person = clusterNodes.get(0).tables()
.table("PUBLIC.PERSON").keyValueView(Integer.class, Person.class);

assertEquals("John", person.get(null, 1).firstname);
assertEquals("Jack", person.get(null, 2).firstname);
assertEquals("Mike", person.get(null, 3).firstname);

updateFunction.accept(dropSql);
}

assertEquals("John", person.get(null, Tuple.create().set("ID", 1)).stringValue("FIRSTNAME"));
assertEquals("Jack", person.get(null, Tuple.create().set("ID", 2)).stringValue("FIRSTNAME"));
assertEquals("Mike", person.get(null, Tuple.create().set("ID", 3)).stringValue("FIRSTNAME"));
private static class Person {
public int age;
public String sid;
public String firstname;
public String lastname;
}
}
@@ -17,13 +17,14 @@

package org.apache.ignite.internal.sql.engine.schema;

import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -303,46 +304,67 @@ private <RowT> BinaryRow insertTuple(RowT row, ExecutionContext<RowT> ectx) {
}

private <RowT> BinaryRow updateTuple(RowT row, List<String> updateColList, ExecutionContext<RowT> ectx) {
RowHandler<RowT> hnd = ectx.rowHandler();

Object2IntMap<String> columnToIndex = new Object2IntOpenHashMap<>(updateColList.size());

for (int i = 0; i < updateColList.size(); i++) {
columnToIndex.put(updateColList.get(i), i + desc.columnsCount());
}

int nonNullVarlenKeyCols = 0;
int nonNullVarlenValCols = 0;

RowHandler<RowT> hnd = ectx.rowHandler();
int offset = desc.columnsCount();
Set<String> toUpdate = new HashSet<>(updateColList);
int keyOffset = schemaDescriptor.keyColumns().firstVarlengthColumn();

for (ColumnDescriptor colDesc : columnsOrderedByPhysSchema) {
if (colDesc.physicalType().spec().fixedLength()) {
continue;
}
if (keyOffset > -1) {
nonNullVarlenKeyCols = countNotNullColumns(
keyOffset,
schemaDescriptor.keyColumns().length(),
columnToIndex, hnd, row);
}

Object val = toUpdate.contains(colDesc.name())
? hnd.get(colDesc.logicalIndex() + offset, row)
: hnd.get(colDesc.logicalIndex(), row);
int valOffset = schemaDescriptor.valueColumns().firstVarlengthColumn();

if (val != null) {
if (colDesc.key()) {
nonNullVarlenKeyCols++;
} else {
nonNullVarlenValCols++;
}
}
if (valOffset > -1) {
nonNullVarlenValCols = countNotNullColumns(
schemaDescriptor.keyColumns().length() + valOffset,
schemaDescriptor.length(),
columnToIndex, hnd, row);
}

RowAssembler rowAssembler = new RowAssembler(schemaDescriptor, nonNullVarlenKeyCols, nonNullVarlenValCols);

for (ColumnDescriptor colDesc : columnsOrderedByPhysSchema) {
RowAssembler.writeValue(
rowAssembler,
colDesc.physicalType(),
toUpdate.contains(colDesc.name())
? hnd.get(colDesc.logicalIndex() + offset, row)
: hnd.get(colDesc.logicalIndex(), row)
);
int orDefault = columnToIndex.getOrDefault(colDesc.name(), colDesc.logicalIndex());

Object val = hnd.get(orDefault, row);

RowAssembler.writeValue(rowAssembler, colDesc.physicalType(), val);
}

return rowAssembler.build();
}

private <RowT> int countNotNullColumns(int start, int end, Object2IntMap<String> columnToIndex,
RowHandler<RowT> hnd, RowT row) {
int nonNullCols = 0;

for (int i = start; i < end; i++) {
ColumnDescriptor colDesc = Objects.requireNonNull(columnsOrderedByPhysSchema.get(i));

assert !colDesc.physicalType().spec().fixedLength();

int colIdInRow = columnToIndex.getOrDefault(colDesc.name(), colDesc.logicalIndex());

if (hnd.get(colIdInRow, row) != null) {
nonNullCols++;
}
}

return nonNullCols;
}

private <RowT> BinaryRow deleteTuple(RowT row, ExecutionContext<RowT> ectx) {
int nonNullVarlenKeyCols = 0;

0 comments on commit d0eab12

Please sign in to comment.