blob: ee98b8a91b926874bd05790692354e30467f4dda [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.ignite.internal.sql.engine;
import static org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException;
import static org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrows;
import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.SubmissionPublisher;
import org.apache.ignite.Ignite;
import org.apache.ignite.internal.ClusterPerClassIntegrationTest;
import org.apache.ignite.lang.ErrorGroups.Sql;
import org.apache.ignite.lang.MarshallerException;
import org.apache.ignite.sql.ResultSet;
import org.apache.ignite.sql.SqlRow;
import org.apache.ignite.table.DataStreamerItem;
import org.apache.ignite.table.KeyValueView;
import org.apache.ignite.table.RecordView;
import org.apache.ignite.table.Table;
import org.apache.ignite.table.Tuple;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
/**
* NOT NULL constraint tests.
*/
public abstract class ItNotNullConstraintTest extends ClusterPerClassIntegrationTest {
@AfterEach
public void clear() {
dropAllTables();
}
protected abstract Ignite ignite();
protected final Table table(String name) {
return ignite().tables().table(name);
}
protected void runSql(String stmt) {
Ignite ignite = ignite();
try (ResultSet<SqlRow> rs = ignite.sql().execute(null, stmt)) {
assertNotNull(rs);
}
}
@Test
public void testSqlNotNullConstraints() {
sql("CREATE TABLE t1 (id INTEGER PRIMARY KEY, int_col INTEGER NOT NULL)");
// INSERT
assertThrowsSqlException(
Sql.CONSTRAINT_VIOLATION_ERR,
"Column 'ID' does not allow NULLs",
() -> runSql("INSERT INTO t1 VALUES(NULL, 1)"));
// KV case
assertThrowsSqlException(
Sql.CONSTRAINT_VIOLATION_ERR,
"Column 'INT_COL' does not allow NULLs",
() -> runSql("INSERT INTO t1 VALUES(1, NULL)"));
// General case
assertThrowsSqlException(
Sql.CONSTRAINT_VIOLATION_ERR,
"Column 'INT_COL' does not allow NULLs",
() -> runSql("INSERT INTO t1 VALUES(1, (SELECT NULL))"));
assertThrowsSqlException(
Sql.CONSTRAINT_VIOLATION_ERR,
"Column 'INT_COL' does not allow NULLs",
() -> runSql("INSERT INTO t1 SELECT 1, NULL"));
// UPDATE
sql("INSERT INTO t1 VALUES(1, 42)");
assertThrowsSqlException(
Sql.STMT_VALIDATION_ERR,
"Cannot update field \"ID\". Primary key columns are not modifiable",
() -> runSql("UPDATE t1 SET id = NULL WHERE val = 42"));
// KV case
assertThrowsSqlException(
Sql.CONSTRAINT_VIOLATION_ERR,
"Column 'INT_COL' does not allow NULLs",
() -> runSql("UPDATE t1 SET int_col = null WHERE id = 1"));
// General case
assertThrowsSqlException(
Sql.CONSTRAINT_VIOLATION_ERR,
"Column 'INT_COL' does not allow NULLs",
() -> runSql("UPDATE t1 SET int_col = null"));
// MERGE
runSql("CREATE TABLE t2 (id INTEGER PRIMARY KEY, int_col INTEGER NOT NULL)");
runSql("INSERT INTO t2 VALUES (1, 42)");
assertThrowsSqlException(
Sql.CONSTRAINT_VIOLATION_ERR,
"Column 'INT_COL' does not allow NULLs",
() -> runSql("MERGE INTO t2 dst USING t1 src ON dst.id = src.id WHEN MATCHED THEN UPDATE SET int_col = NULL"));
runSql("INSERT INTO t1 VALUES (2, 71)");
assertThrowsSqlException(
Sql.CONSTRAINT_VIOLATION_ERR,
"Column 'INT_COL' does not allow NULLs",
() -> runSql("MERGE INTO t2 dst USING t1 src ON dst.id = src.id "
+ "WHEN NOT MATCHED THEN INSERT (id, int_col) VALUES (src.id, NULL)"));
}
@Test
public void testKeyValueView() {
sql("CREATE TABLE kv (id INTEGER PRIMARY KEY, val INTEGER NOT NULL)");
Table table = table("KV");
{
KeyValueView<Integer, Integer> view = table.keyValueView(Integer.class, Integer.class);
assertThrows(MarshallerException.class, () -> {
view.put(null, 1, null);
}, "Column 'VAL' does not allow NULLs");
}
{
KeyValueView<Tuple, Tuple> view = table.keyValueView();
assertThrows(MarshallerException.class, () -> {
view.put(null, Tuple.create(Map.of("id", 1)), Tuple.create());
}, "Column 'VAL' does not allow NULLs");
}
{
KeyValueView<Integer, Val> view = table.keyValueView(Integer.class, Val.class);
assertThrows(MarshallerException.class, () -> {
Val val = new Val();
view.put(null, 1, val);
}, "Column 'VAL' does not allow NULLs");
}
}
@Test
public void testRecordView() {
sql("CREATE TABLE kv (id INTEGER PRIMARY KEY, val INTEGER NOT NULL)");
Table table = table("KV");
{
RecordView<Rec> view = table.recordView(Rec.class);
assertThrows(MarshallerException.class, () -> {
Rec rec = new Rec();
view.insert(null, rec);
}, "Column 'ID' does not allow NULLs");
}
{
RecordView<Rec> view = table.recordView(Rec.class);
assertThrows(MarshallerException.class, () -> {
Rec rec = new Rec();
rec.id = 42;
view.insert(null, rec);
}, "Column 'VAL' does not allow NULLs");
}
{
RecordView<Tuple> view = table.recordView();
assertThrows(MarshallerException.class, () -> {
view.insert(null, Tuple.create(Map.of("id", 1)));
}, "Column 'VAL' does not allow NULLs");
}
}
@Test
public void testKeyValueViewDataStreamer() {
sql("CREATE TABLE kv (id INTEGER PRIMARY KEY, val INTEGER NOT NULL)");
Table table = table("KV");
{
KeyValueView<Tuple, Tuple> view = table.keyValueView();
checkDataStreamer(view, new SimpleEntry<>(Tuple.create(Map.of("id", 1)), Tuple.create()), "Column 'VAL' does not allow NULLs");
}
{
KeyValueView<Integer, Integer> view = table.keyValueView(Integer.class, Integer.class);
checkDataStreamer(view, new SimpleEntry<>(1, null), "Column 'VAL' does not allow NULLs");
}
{
KeyValueView<Integer, Val> view = table.keyValueView(Integer.class, Val.class);
Val val = new Val();
checkDataStreamer(view, new SimpleEntry<>(1, val), "Column 'VAL' does not allow NULLs");
}
}
@Test
public void testRecordViewDataStreamer() {
sql("CREATE TABLE kv (id INTEGER PRIMARY KEY, val INTEGER NOT NULL)");
Table table = table("KV");
{
RecordView<Tuple> view = table.recordView();
checkDataStreamer(view, Tuple.create(Map.of("id", 1)), "Column 'VAL' does not allow NULLs");
}
{
RecordView<Rec> view = table.recordView(Rec.class);
Rec rec = new Rec();
checkDataStreamer(view, rec, "Column 'ID' does not allow NULLs");
}
{
RecordView<Rec> view = table.recordView(Rec.class);
Rec rec = new Rec();
rec.id = 1;
checkDataStreamer(view, rec, "Column 'VAL' does not allow NULLs");
}
}
private static <K, V> void checkDataStreamer(KeyValueView<K, V> view, Entry<K, V> item, String error) {
CompletableFuture<Void> streamerFut;
try (var publisher = new SubmissionPublisher<DataStreamerItem<Entry<K, V>>>()) {
streamerFut = view.streamData(publisher, null);
publisher.submit(DataStreamerItem.of(item));
}
assertThrows(MarshallerException.class, () -> await(streamerFut), error);
}
private static <R> void checkDataStreamer(RecordView<R> view, R item, String error) {
CompletableFuture<Void> streamerFut;
try (var publisher = new SubmissionPublisher<DataStreamerItem<R>>()) {
streamerFut = view.streamData(publisher, null);
publisher.submit(DataStreamerItem.of(item));
}
assertThrows(MarshallerException.class, () -> await(streamerFut), error);
}
static class Val {
@SuppressWarnings("unused")
Integer val;
}
static class Rec {
Integer id;
@SuppressWarnings("unused")
Integer val;
}
}