/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.server.test.it.qp; import com.foundationdb.qp.operator.Cursor; import com.foundationdb.qp.operator.Operator; import com.foundationdb.qp.rowtype.TableRowType; import com.foundationdb.qp.row.IndexRow; import com.foundationdb.qp.row.Row; import com.foundationdb.qp.rowtype.IndexRowType; import com.foundationdb.qp.rowtype.Schema; import org.junit.Test; import static com.foundationdb.qp.operator.API.cursor; import static com.foundationdb.qp.operator.API.indexScan_Default; import static org.junit.Assert.*; public class UniqueIndexUpdateIT extends OperatorITBase { @Override protected void setupCreateSchema() { t = createTable( "schema", "t", "id int not null", "x int", "y int", "primary key (id)"); createUniqueIndex("schema", "t", "idx_xy", "x", "y"); } @Override protected void setupPostCreateSchema() { tRowType = schema.tableRowType(table(t)); xyIndexRowType = indexType(t, "x", "y"); queryContext = queryContext(adapter); queryBindings = queryContext.createBindings(); } @Test public void testNullOnInsert() { writeRow(t, 1000L, 1L, 1L); writeRow(t, 2000L, 2L, 2L); writeRow(t, 3000L, 3L, null); writeRow(t, 4000L, 4L, null); Operator plan = indexScan_Default(xyIndexRowType); Cursor cursor = cursor(plan, queryContext, queryBindings); cursor.openTopLevel(); Row row; int count = 0; while ((row = cursor.next()) != null) { IndexRow indexRow = (IndexRow) row; long x = getLong(indexRow, 0); long id = getLong(indexRow, 2); assertEquals(id, x * 1000); switch((int)x) { case 1: case 2: assertEquals(x, getLong(indexRow, 1).longValue()); break; case 3: case 4: assertTrue(isNull(indexRow, 1)); break; default: fail(); } count++; } assertEquals(4, count); } @Test public void testNullOnUpdate() { // Load as in testNullSeparatorOnInsert writeRow(t, 1000L, 1L, 1L); writeRow(t, 2000L, 2L, 2L); writeRow(t, 3000L, 3L, null); writeRow(t, 4000L, 4L, null); // Change nulls to some other value. Scan backwards to avoid halloween issues. Cursor cursor = cursor(indexScan_Default(xyIndexRowType, true), queryContext, queryBindings); cursor.openTopLevel(); Row row; final long NEW_Y_VALUE = 99; while ((row = cursor.next()) != null) { IndexRow indexRow = (IndexRow) row; long x = getLong(indexRow, 0); long id = getLong(indexRow, 2); int pos = 1; if (isNull(indexRow, pos)) { Row oldRow = row(t, id, x, null); Row newRow = row(t, id, x, NEW_Y_VALUE); updateRow(oldRow, newRow); } } cursor.close(); // Check final state cursor = cursor(indexScan_Default(xyIndexRowType), queryContext, queryBindings); cursor.openTopLevel(); int count = 0; while ((row = cursor.next()) != null) { IndexRow indexRow = (IndexRow) row; long x = getLong(indexRow, 0); long y = getLong(indexRow, 1); long id = getLong(indexRow, 2); assertEquals(id, x * 1000); if (id <= 2000) { assertEquals(id, y * 1000); } else { assertEquals(NEW_Y_VALUE, y); } count++; } assertEquals(4, count); } @Test public void testDeleteIndexRowWithNull() { writeRow(t, 1L, 999L, null); writeRow(t, 2L, 999L, null); writeRow(t, 3L, 999L, null); writeRow(t, 4L, 999L, null); writeRow(t, 5L, 999L, null); writeRow(t, 6L, 999L, null); checkIndex(1, 2, 3, 4, 5, 6); // Delete each row deleteRow(t, 3L, 999L, null); checkIndex(1, 2, 4, 5, 6); deleteRow(t, 6L, 999L, null); checkIndex(1, 2, 4, 5); deleteRow(t, 2L, 999L, null); checkIndex(1, 4, 5); deleteRow(t, 4L, 999L, null); checkIndex(1, 5); deleteRow(t, 1L, 999L, null); checkIndex(5); deleteRow(t, 5L, 999L, null); checkIndex(); } private void checkIndex(long ... expectedIds) { Cursor cursor = cursor(indexScan_Default(xyIndexRowType), queryContext, queryBindings); cursor.openTopLevel(); Row row; int count = 0; while ((row = cursor.next()) != null) { long id = getLong(row, 2); assertEquals(expectedIds[count], id); count++; } assertEquals(expectedIds.length, count); } // Inspired by bug 1036389 @Test public void testUpdateIndexRowWithNull() { db = new Row[]{ row(t, 1L, null, null), }; use(db); Row oldRow = row(t, 1L, null, null); Row newRow = row(t, 1L, 10L, 10L); updateRow(oldRow, newRow); Cursor cursor = cursor(indexScan_Default(xyIndexRowType), queryContext, queryBindings); cursor.openTopLevel(); Row row = cursor.next(); assertEquals(Long.valueOf(10), getLong(row, 0)); assertEquals(Long.valueOf(10), getLong(row, 1)); assertEquals(Long.valueOf(1), getLong(row, 2)); row = cursor.next(); assertNull(row); } private int t; private TableRowType tRowType; private IndexRowType xyIndexRowType; }