/** * 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.keyupdate; import com.foundationdb.ais.model.AkibanInformationSchema; import com.foundationdb.ais.model.Group; import com.foundationdb.ais.model.GroupIndex; import com.foundationdb.ais.model.Table; import com.foundationdb.ais.model.TableName; import com.foundationdb.server.store.IndexRecordVisitor; import com.foundationdb.server.test.it.ITBase; import com.foundationdb.util.AssertUtils; import com.foundationdb.util.Exceptions; import com.foundationdb.util.Strings; import com.foundationdb.util.tap.Tap; import com.foundationdb.util.tap.TapReport; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * <p>Various atomic tests of GI maintenance.</p> * *<p>Each test is named foo_action_X where: * <ul> * <li>foo = the initial state of the db</li> * <li>action = add, del (delete), move, update</li> * <li>X = one of {@code [c a o i h]}, the type of row which is being added/deleted/moved</li> * </ul></p> * * The grouping is: * <pre> * c * |- a * |- o * |- i * |- h</pre> * <p>For instance, {@link #coh_add_i()} would start with a db where there's a customer with an order, and an orphaned * handling row; it would then add the item that links the customer-order to the handling, and check the GIs.</p> * * <p>About the PKs: each one has as many digits as its depth, and its rightmost (N-1) digits correspond to its * parent. For instance, an item row would have a 3-digits PK (its depth is 3), and its 2 rightmost digits would * refer to its parent order. This is true even if the given order doesn't exist. The leftmost digit simply increments. * So, the first order for cid 1 would have a PK of 11, and the next order for that customer would be 21. The first * order for cid 2 would have a PK of 12.</p> * * <p>The _extra columns in each table are always initialized to the (PK value) * 1000. * * <p>Some of these tests contain multiple branches. These have names like {@code coihCIH_move_o()}. In this case, * we have two branches initially: one has coih rows and the other has c, no o, and an orphaned i that has an h. It then * moves the o from the first branch to the second, such that the first now has an orphaned i, and the second adopts * and now has an unbroken branch.</p> */ public final class NewGiUpdateIT extends ITBase { private GisChecker initC() { writeRow(c, 117L, "John", 117000L); return checker() .gi(___LEFT_name_when___________) .entry("John, null, 117, null").backedBy(c) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } @Test public void c_add_c() { GisChecker initState = initC(); writeRow(c, 6L, "Noble", 6000L); checker() .gi(___LEFT_name_when___________) .entry("John, null, 117, null").backedBy(c) .entry("Noble, null, 6, null").backedBy(c) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(c, 6L); initState.check(); } @Test public void c_add_o() { GisChecker initState = initC(); writeRow(o, 7L, 117L, "2552-08-30", 7000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2552-08-30, 117, 7").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 2552-08-30, 117, 7").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(o, 7L, 117L); initState.check(); } @Test public void c_add_i() { GisChecker initState = initC(); writeRow(i, 101L, 7L, "1234", 101000L); checker() .gi(___LEFT_name_when___________) .entry("John, null, 117, null").backedBy(c) .gi(___LEFT_sku_instructions____) .entry("1234, null, null, 7, 101, null").backedBy(i) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(i, 101L, 7L); initState.check(); } @Test public void c_add_h() { GisChecker initState = initC(); writeRow(h, 1001L, 101L, "don't drop", 1001000L); checker() .gi(___LEFT_name_when___________) .entry("John, null, 117, null").backedBy(c) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .entry("null, don't drop, null, null, 101, 1001").backedBy(h) .gi(___RIGHT_street_name________) .done(); deleteRow(h, 1001L, 101L); initState.check(); } @Test public void c_add_a() { GisChecker initState = initC(); writeRow(a, 40L, 117L, "Reach", 40000L); checker() .gi(___LEFT_name_when___________) .entry("John, null, 117, null").backedBy(c) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .entry("Reach, John, 117, 40").backedBy(c, a) .done(); deleteRow(a, 40L, 117L); initState.check(); } @Test public void c_del_c() { initC(); deleteRow(c, 117L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } private GisChecker init_cC() { writeRow(c, 6L, "Noble", 6000L); writeRow(c, 117L, "John", 117000L); return checker() .gi(___LEFT_name_when___________) .entry("John, null, 117, null").backedBy(c) .entry("Noble, null, 6, null").backedBy(c) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } @Test public void cC_add_o() { GisChecker initState = init_cC(); writeRow(o, 10L, 117L, "1970-01-01", 10000L); checker() .gi(___LEFT_name_when___________) .entry("John, 1970-01-01, 117, 10").backedBy(c, o) .entry("Noble, null, 6, null").backedBy(c) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 1970-01-01, 117, 10").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(o, 10L, 117L); initState.check(); } @Test public void cC_del_c() { init_cC(); deleteRow(c, 117L); checker() .gi(___LEFT_name_when___________) .entry("Noble, null, 6, null").backedBy(c) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } private GisChecker init_o() { writeRow(o, 11L, 1L, "2001-01-01", 11000L); return checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } @Test public void o_add_c() { GisChecker initState = init_o(); writeRow(c, 1L, "Bob", 1000L); checker() .gi(___LEFT_name_when___________) .entry("Bob, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("Bob, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(c, 1L); initState.check(); } @Test public void o_add_o() { GisChecker initState = init_o(); writeRow(o, 21L, 1L, "2002-02-02", 21000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .entry("null, 2002-02-02, 1, 21").backedBy(o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(o, 21L, 1L); initState.check(); } @Test public void o_add_i() { GisChecker initState = init_o(); writeRow(i, 111L, 11L, "1234", 111000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, null, 1, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(i, 111L, 11L); initState.check(); } @Test public void o_add_h() { GisChecker initState = init_o(); writeRow(h, 1111L, 111L, "careful", 1111000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .gi(___RIGHT_sku_instructions___) .entry("null, careful, null, null, 111, 1111").backedBy(h) .gi(___RIGHT_street_name________) .done(); deleteRow(h, 1111L, 111L); initState.check(); } @Test public void o_add_a() { GisChecker initState = init_o(); writeRow(a, 11L, 1L, "Harrison Ave", 11000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .entry("Harrison Ave, null, 1, 11").backedBy(a) .done(); deleteRow(a, 11L, 1L); initState.check(); } private GisChecker init_oo() { writeRow(o, 11L, 1L, "2001-01-01", 11000L); writeRow(o, 21L, 1L, "2002-02-02", 21000L); return checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .entry("null, 2002-02-02, 1, 21").backedBy(o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } @Test public void oo_add_c() { GisChecker initState = init_oo(); writeRow(c, 1L, "John", 1000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .entry("John, 2002-02-02, 1, 21").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .entry("John, 2002-02-02, 1, 21").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(c, 1L); initState.check(); } @Test public void oo_add_i() { GisChecker initState = init_oo(); writeRow(i, 111L, 11L, "1234", 111000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, null, 1, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .entry("null, 2002-02-02, 1, 21").backedBy(o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(i, 111L, 11L); initState.check(); } @Test public void oo_add_h() { GisChecker initState = init_oo(); writeRow(h, 1111L, 111L, "careful", 1111000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .entry("null, 2002-02-02, 1, 21").backedBy(o) .gi(___RIGHT_sku_instructions___) .entry("null, careful, null, null, 111, 1111").backedBy(h) .gi(___RIGHT_street_name________) .done(); deleteRow(h, 1111L, 111L); initState.check(); } @Test public void oo_del_o() { init_oo(); deleteRow(o, 11L, 1L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("null, 2002-02-02, 1, 21").backedBy(o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } private GisChecker init_i() { writeRow(i, 111L, 11L, "1234", 111000L); return checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, null, null, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } @Test public void i_add_c() { GisChecker initState = init_i(); writeRow(c, 1L, "John", 1000L); checker() .gi(___LEFT_name_when___________) .entry("John, null, 1, null").backedBy(c) .gi(___LEFT_sku_instructions____) .entry("1234, null, null, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(c, 1L); initState.check(); } @Test public void i_add_o() { GisChecker initState = init_i(); writeRow(o, 11L, 1L, "2001-01-01", 11000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, null, 1, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(o, 11L, 1L); initState.check(); } @Test public void i_add_i() { GisChecker initState = init_i(); writeRow(i, 211L, 11L, "5678", 211000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, null, null, 11, 111, null").backedBy(i) .entry("5678, null, null, 11, 211, null").backedBy(i) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(i, 211L, 11L); initState.check(); } @Test public void i_add_h() { GisChecker initState = init_i(); writeRow(h, 1111L, 111L, "don't drop", 1111000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, null, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, null, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); deleteRow(h, 1111L, 111L); initState.check(); } @Test public void i_add_a() { GisChecker initState = init_i(); writeRow(a, 11L, 1L, "Mass Ave", 11000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, null, null, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .entry("Mass Ave, null, 1, 11").backedBy(a) .done(); deleteRow(a, 11L, 1L); initState.check(); } private GisChecker init_ii() { writeRow(i, 111L, 11L, "1234", 111000L); writeRow(i, 211L, 11L, "5678", 211000L); return checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, null, null, 11, 111, null").backedBy(i) .entry("5678, null, null, 11, 211, null").backedBy(i) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } @Test public void ii_add_c() { GisChecker initState = init_ii(); writeRow(c, 1L, "John", 1000L); checker() .gi(___LEFT_name_when___________) .entry("John, null, 1, null").backedBy(c) .gi(___LEFT_sku_instructions____) .entry("1234, null, null, 11, 111, null").backedBy(i) .entry("5678, null, null, 11, 211, null").backedBy(i) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(c, 1L); initState.check(); } @Test public void ii_add_o() { GisChecker initState = init_ii(); writeRow(o, 11L, 1L, "2001-01-01", 11000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, null, 1, 11, 111, null").backedBy(i) .entry("5678, null, 1, 11, 211, null").backedBy(i) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(o, 11L, 1L); initState.check(); } @Test public void ii_add_h() { GisChecker initState = init_ii(); writeRow(h, 1111L, 111L, "don't drop!", 1111000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop!, null, 11, 111, 1111").backedBy(i, h) .entry("5678, null, null, 11, 211, null").backedBy(i) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop!, null, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); deleteRow(h, 1111L, 111L); initState.check(); } @Test public void ii_del_i() { init_ii(); deleteRow(i, 111L, 11L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("5678, null, null, 11, 211, null").backedBy(i) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } private GisChecker initH() { writeRow(h, 1111L, 111L, "don't let it break", 1111000L); return checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .entry("null, don't let it break, null, null, 111, 1111").backedBy(h) .gi(___RIGHT_street_name________) .done(); } @Test public void h_add_c() { GisChecker initState = initH(); writeRow(c, 1L, "John", 1000L); checker() .gi(___LEFT_name_when___________) .entry("John, null, 1, null").backedBy(c) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .entry("null, don't let it break, null, null, 111, 1111").backedBy(h) .gi(___RIGHT_street_name________) .done(); deleteRow(c, 1L, "John"); initState.check(); } @Test public void h_add_o() { GisChecker initState = initH(); writeRow(o, 11L, 1L, "2001-01-01", 11000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .gi(___RIGHT_sku_instructions___) .entry("null, don't let it break, null, null, 111, 1111").backedBy(h) .gi(___RIGHT_street_name________) .done(); deleteRow(o, 11L, 1L); initState.check(); } @Test public void h_add_i() { GisChecker initState = initH(); writeRow(i, 111L, 11L, "1234", 111000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, don't let it break, null, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .entry("1234, don't let it break, null, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); deleteRow(i, 111L, 11L); initState.check(); } @Test public void h_add_h() { GisChecker initState = initH(); writeRow(h, 2111L, 111L, "it's fine if it breaks", 2111000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .entry("null, don't let it break, null, null, 111, 1111").backedBy(h) .entry("null, it's fine if it breaks, null, null, 111, 2111").backedBy(h) .gi(___RIGHT_street_name________) .done(); deleteRow(h, 2111L, 111L); initState.check(); } private GisChecker init_co() { writeRow(o, 11L, 1L, "2001-01-01", 11000L); writeRow(c, 1L, "John", 1000L); return checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } @Test public void co_add_o() { GisChecker initState = init_co(); writeRow(o, 21L, 1L, "2002-02-02", 21000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .entry("John, 2002-02-02, 1, 21").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .entry("John, 2002-02-02, 1, 21").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(o, 21L, 1L); initState.check(); } @Test public void co_add_i() { GisChecker initState = init_co(); writeRow(i, 111L, 11L, "1234", 111000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, null, 1, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(i, 111L, 11L); initState.check(); } @Test public void co_add_h() { GisChecker initState = init_co(); writeRow(h, 1111L, 111L, "don't drop", 1111000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("null, don't drop, null, null, 111, 1111").backedBy(h) .gi(___RIGHT_street_name________) .done(); deleteRow(h, 1111L, 111L); initState.check(); } @Test public void co_del_c() { init_co(); deleteRow(c, 1L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } @Test public void co_del_o() { init_co(); deleteRow(o, 11L, 1L); checker() .gi(___LEFT_name_when___________) .entry("John, null, 1, null").backedBy(c) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } private GisChecker init_ci() { writeRow(c, 1L, "John", 1000L); writeRow(i, 111L, 11L, "1234", 111000L); return checker() .gi(___LEFT_name_when___________) .entry("John, null, 1, null").backedBy(c) .gi(___LEFT_sku_instructions____) .entry("1234, null, null, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } @Test public void ci_add_o() { GisChecker initState = init_ci(); writeRow(o, 11L, 1L, "2001-01-01", 11000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, null, 1, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(o, 11L, 1L); initState.check(); } @Test public void ci_add_h() { GisChecker initState = init_ci(); writeRow(h, 1111L, 111L, "don't drop", 1111000L); checker() .gi(___LEFT_name_when___________) .entry("John, null, 1, null").backedBy(c) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, null, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, null, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); deleteRow(h, 1111L, 111L); initState.check(); } private GisChecker init_oi() { writeRow(o, 11L, 1L, "2001-01-01", 11000L); writeRow(i, 111L, 11L, "1234", 111000L); return checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, null, 1, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } @Test public void oi_add_c() { GisChecker initState = init_oi(); writeRow(c, 1L, "John", 1000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, null, 1, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); deleteRow(c, 1L, "John"); initState.check(); } @Test public void oi_add_h() { GisChecker initState = init_oi(); writeRow(h, 1111L, 111L, "don't drop", 1111000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); deleteRow(h, 1111L, 111L); initState.check(); } @Test public void oi_del_o() { init_oi(); deleteRow(o, 11L, 1L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, null, null, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } @Test public void oi_del_i() { init_oi(); deleteRow(i, 111L, 11L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } @Test public void ih_add_o() { writeRow(i, 111L, 11L, "1234", 111000L); writeRow(h, 1111L, 111L, "don't drop", 1111000L); GisChecker initState = checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, null, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, null, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); writeRow(o, 11L, 1L, "2001-01-01", 11000L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); deleteRow(o, 11L, 1L); initState.check(); } private GisChecker init_coi() { writeRow(c, 1L, "John", 1000L); writeRow(o, 11L, 1L, "2001-01-01", 11000L); writeRow(i, 111L, 11L, "1234", 111000L); return checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, null, 1, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } @Test public void coi_add_h() { GisChecker initState = init_coi(); writeRow(h, 1111L, 111L, "don't drop", 1111000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); deleteRow(h, 1111L, 111L); initState.check(); } @Test public void coi_add_a() { GisChecker initState = init_coi(); writeRow(a, 11L, 1L, "Harrison", 11000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, null, 1, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .entry("Harrison, John, 1, 11").backedBy(c, a) .done(); deleteRow(a, 11L, 1L); initState.check(); } @Test public void coh_add_i() { writeRow(c, 1L, "John", 1000L); writeRow(o, 11L, 1L, "2001-01-01", 11000L); writeRow(h, 1111L, 111L, "don't drop!", 1111000L); GisChecker initState = checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("null, don't drop!, null, null, 111, 1111").backedBy(h) .gi(___RIGHT_street_name________) .done(); writeRow(i, 111L, 11L, "1234", 111000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop!, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop!, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); deleteRow(i, 111L, 11L); initState.check(); } private GisChecker init_coih() { writeRow(c, 1L, "John", 1000L); writeRow(o, 11L, 1L, "2001-01-01", 11000L); writeRow(i, 111L, 11L, "1234", 111000L); writeRow(h, 1111L, 111L, "don't drop", 1111000L); return checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); } @Test public void coih_add_c() { GisChecker initState = init_coih(); writeRow(c, 2L, "Bob", 2000L); checker() .gi(___LEFT_name_when___________) .entry("Bob, null, 2, null").backedBy(c) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); deleteRow(c, 2L); initState.check(); } @Test public void coih_add_o() { GisChecker initState = init_coih(); writeRow(o, 21L, 1L, "2002-02-02", 21000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .entry("John, 2002-02-02, 1, 21").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .entry("John, 2002-02-02, 1, 21").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); deleteRow(o, 21L, 1L); initState.check(); } @Test public void coih_add_i() { GisChecker initState = init_coih(); writeRow(i, 211L, 11L, "5678", 211000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .entry("5678, null, 1, 11, 211, null").backedBy(i) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); deleteRow(i, 211L, 11L); initState.check(); } @Test public void coih_add_h() { GisChecker initState = init_coih(); writeRow(h, 2111L, 111L, "fine if it drops", 2111000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .entry("1234, fine if it drops, 1, 11, 111, 2111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .entry("1234, fine if it drops, 1, 11, 111, 2111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); deleteRow(h, 2111L, 111L); initState.check(); } @Test public void coih_add_a() { GisChecker initState = init_coih(); writeRow(a, 11L, 1L, "Mass Ave", 11000L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .entry("Mass Ave, John, 1, 11").backedBy(c, a) .done(); deleteRow(a, 11L, 1L); initState.check(); } @Test public void coih_del_c() { init_coih(); deleteRow(c, 1L); checker() .gi(___LEFT_name_when___________) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .gi(___RIGHT_street_name________) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .done(); } @Test public void coih_del_o() { init_coih(); deleteRow(o, 11L, 1L); checker() .gi(___LEFT_name_when___________) .entry("John, null, 1, null").backedBy(c) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, null, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .gi(___RIGHT_street_name________) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, null, 11, 111, 1111").backedBy(i, h) .done(); } @Test public void coih_del_i() { init_coih(); deleteRow(i, 111L, 11L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_street_name________) .gi(___RIGHT_sku_instructions___) .entry("null, don't drop, null, null, 111, 1111").backedBy(h) .done(); } @Test public void coih_del_h() { init_coih(); deleteRow(h, 1111L, 111L); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, null, 1, 11, 111, null").backedBy(i) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .done(); } private void init_coih_COIH(int... which) { init_coih(); Set<Integer> whichSet = new HashSet<>(); for (int whichInt : which) whichSet.add(whichInt); assertFalse("no COIH tables provided", whichSet.isEmpty()); for (int whichInt : whichSet) { if (whichInt == c) writeRow(c, 2L, "Bob", 2000L); else if (whichInt == o) writeRow(o, 12L, 2L, "2002-02-02", 12000L); else if (whichInt == i) writeRow(i, 112L, 12L, "5678", 112000L); else if (whichInt == h) writeRow(h, 1112L, 112L, "be careful", 1112000L); else throw new RuntimeException("unknown table id: " + whichInt); } } @Test public void coihOIH_move_c() { init_coih_COIH(o, i, h); GisChecker initState = checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .entry("5678, be careful, 2, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("null, 2002-02-02, 2, 12").backedBy(o) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .entry("5678, be careful, 2, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); updateRow(row(c, 1L, "John", 1000L), row(c, 2L, "Johnny", 1000L)); checker() .gi(___LEFT_name_when___________) .entry("Johnny, 2002-02-02, 2, 12").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .entry("5678, be careful, 2, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("null, 2001-01-01, 1, 11").backedBy(o) .entry("Johnny, 2002-02-02, 2, 12").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .entry("5678, be careful, 2, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); updateRow(row(c, 2L, "Johnny", 1000L), row(c, 1L, "John", 1000L)); initState.check(); } private GisChecker init_coihCIH() { init_coih_COIH(c, i, h); return checker() .gi(___LEFT_name_when___________) .entry("Bob, null, 2, null").backedBy(c) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .entry("5678, be careful, null, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .entry("5678, be careful, null, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); } @Test public void coihCIH_move_o_changeCid() { GisChecker initState = init_coihCIH(); updateRow(row(o, 11L, 1L, "2001-01-1", 11000L), row(o, 21L, 1L, "1999-12-31", 11000L)); checker() .gi(___LEFT_name_when___________) .entry("Bob, null, 2, null").backedBy(c) .entry("John, 1999-12-31, 1, 21").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, null, 11, 111, 1111").backedBy(i, h) .entry("5678, be careful, null, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 1999-12-31, 1, 21").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, null, 11, 111, 1111").backedBy(i, h) .entry("5678, be careful, null, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); updateRow(row(o, 21L, 1L, "1999-12-31", 11000L), row(o, 11L, 1L, "2001-01-01", 11000L)); initState.check(); } @Test public void coihCIH_move_o_changeOid() { GisChecker initState = init_coihCIH(); // This one's a bit tricky, because the pks/fks don't match up anymore in terms of our naming conventions. // Once the update is done, oid=11 now belongs to cid=2, but it doesn't change its children items. That means // the items' PK have to be adjusted, but only at the cid level. // // Before: // c 1 1 John // o 1,11 11 1 2001-01-01 // i 1,11,111 111 11 1234 // h 1,11,111,1111 1111 111 don't drop // c 2 2 Bob // i 2,12,112 112 12 5678 // h 2,12,112,1112 1112 112 be careful // // After: // c 1 1 John // c 2 2 Bob // o 2,11 11 1 2001-01-01 // i 2,11,111 111 11 1234 // h 2,11,111,1111 1111 111 don't drop // i _,12,112 112 12 5678 // h _,12,112,1112 1112 112 be careful updateRow(row(o, 11L, 1L, "2001-01-1", 11000L), row(o, 11L, 2L, "1999-12-31", 11000L)); checker() .gi(___LEFT_name_when___________) .entry("Bob, 1999-12-31, 2, 11").backedBy(c, o) .entry("John, null, 1, null").backedBy(c) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 2, 11, 111, 1111").backedBy(i, h) // Note! oid=11 still belongs to iid=112 .entry("5678, be careful, null, 12, 112, 1112").backedBy(i, h) // Note! oid=11 doesn't adopt iid=112 .gi(___RIGHT_name_when__________) .entry("Bob, 1999-12-31, 2, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 2, 11, 111, 1111").backedBy(i, h) // these two are like the left joins .entry("5678, be careful, null, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); updateRow(row(o, 11L, 2L, "1999-12-31", 11000L), row(o, 11L, 1L, "2001-01-01", 11000L)); initState.check(); } @Test public void coihCIH_move_o_changeBoth() { GisChecker initState = init_coihCIH(); updateRow(row(o, 11L, 1L, "2001-01-1", 11000L), row(o, 12L, 2L, "1999-12-31", 11000L)); checker() .gi(___LEFT_name_when___________) .entry("Bob, 1999-12-31, 2, 12").backedBy(c, o) .entry("John, null, 1, null").backedBy(c) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, null, 11, 111, 1111").backedBy(i, h) .entry("5678, be careful, 2, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("Bob, 1999-12-31, 2, 12").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, null, 11, 111, 1111").backedBy(i, h) .entry("5678, be careful, 2, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); updateRow(row(o, 12L, 2L, "1999-12-31", 11000L), row(o, 11L, 1L, "2001-01-01", 11000L)); initState.check(); } @Test public void coihCOH_move_i() { init_coih_COIH(c, o, h); GisChecker initState = checker() .gi(___LEFT_name_when___________) .entry("Bob, 2002-02-02, 2, 12").backedBy(c, o) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("Bob, 2002-02-02, 2, 12").backedBy(c, o) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("null, be careful, null, null, 112, 1112").backedBy(h) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); updateRow(row(i, 111L, 11L, "1234", 111000L), row(i, 112L, 12L, "3456", 111000L)); checker() .gi(___LEFT_name_when___________) .entry("Bob, 2002-02-02, 2, 12").backedBy(c, o) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("3456, be careful, 2, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("Bob, 2002-02-02, 2, 12").backedBy(c, o) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("null, don't drop, null, null, 111, 1111").backedBy(h) .entry("3456, be careful, 2, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); updateRow(row(i, 112L, 12L, "3456", 111000L), row(i, 111L, 11L, "1234", 111000L)); initState.check(); } @Test public void coihCOI_move_h() { init_coih_COIH(c, o, i); GisChecker initState = checker() .gi(___LEFT_name_when___________) .entry("Bob, 2002-02-02, 2, 12").backedBy(c, o) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .entry("5678, null, 2, 12, 112, null").backedBy(i) .gi(___RIGHT_name_when__________) .entry("Bob, 2002-02-02, 2, 12").backedBy(c, o) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); updateRow(row(h, 1111L, 111L, "don't drop"), row(h, 1112L, 112L, "handle with care")); checker() .gi(___LEFT_name_when___________) .entry("Bob, 2002-02-02, 2, 12").backedBy(c, o) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, null, 1, 11, 111, null").backedBy(i) .entry("5678, handle with care, 2, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("Bob, 2002-02-02, 2, 12").backedBy(c, o) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("5678, handle with care, 2, 12, 112, 1112").backedBy(i, h) .gi(___RIGHT_street_name________) .done(); updateRow(row(h, 1112L, 112L, "handle with care"), row(h, 1111L, 111L, "don't drop")); initState.check(); } // Tests of GI maintenance optimization -- avoiding maintenance when unaffected columns are updated. @Test public void update_c_skip_not_possible_1() { init_co(); // Update name, which is involved in all indexes on customer. updateRow(row(c, 1L, "John", 1000L), row(c, 1L, "Paul", 1000L)); checker() .gi(___LEFT_name_when___________) .entry("Paul, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("Paul, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(0) .done(); } @Test public void update_c_skip_not_possible_2() { init_co(); // Updating both name and extra -- same number of GI maintenance actions as for updating name. updateRow(row(c, 1L, "John", 1000L), row(c, 1L, "Paul", 1111L)); checker() .gi(___LEFT_name_when___________) .entry("Paul, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("Paul, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(0) .done(); } @Test public void update_c_skip_no_actual_update() { init_co(); // If there is no actual update, all maintenance (twice for each index involving customer) should be skipped updateRow(row(c, 1L, "John", 1000L), row(c, 1L, "John", 1000L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(6) .done(); } @Test public void update_c_skip_update_unindexed() { init_co(); // There are 3 indexes involving the customer table and each may be updated twice. // If extra is updated, which is not involved in any index, then the expected number // of GI maintenance actions skipped is 6. updateRow(row(c, 1L, "John", 1000L), row(c, 1L, "John", 1111L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(6) .done(); } @Test public void update_o_skip_not_possible_1() { init_co(); // Update when, which is involved in all indexes on order. updateRow(row(o, 11L, 1L, "2001-01-01", 11000L), row(o, 11L, 1L, "2011-11-11", 11000L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2011-11-11, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 2011-11-11, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(0) .done(); } @Test public void update_o_skip_not_possible_2() { init_co(); // Updating both when and extra -- same number of GI maintenance actions as for updating when. updateRow(row(o, 11L, 1L, "2001-01-01", 11000L), row(o, 11L, 1L, "2011-11-11", 11111L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2011-11-11, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 2011-11-11, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(0) .done(); } @Test public void update_o_skip_no_actual_update() { init_co(); // If there is no actual update, all maintenance (twice for each index involving order) should be skipped updateRow(row(o, 11L, 1L, "2001-01-01", 11000L), row(o, 11L, 1L, "2001-01-01", 11000L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(4) .done(); } @Test public void update_o_skip_update_unindexed() { init_co(); // There are 2 indexes involving the order table and each may be updated twice. // If extra is updated, which is not involved in any index, then the expected number // of GI maintenance actions skipped is 4. updateRow(row(o, 11L, 1L, "2001-01-01", 11000L), row(o, 11L, 1L, "2001-01-01", 11111L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(4) .done(); } @Test public void update_i_skip_not_possible_1() { init_coih(); // Update sku, which is involved in all indexes on item. updateRow(row(i, 111L, 11L, "1234", 111000L), row(i, 111L, 11L, "9999", 111000L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("9999, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("9999, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(0) .done(); } @Test public void update_i_skip_not_possible_2() { init_coih(); // Updating both sku and extra -- same number of GI maintenance actions as for updating sku. updateRow(row(i, 111L, 11L, "1234", 111000L), row(i, 111L, 11L, "9999", 111111L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("9999, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("9999, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(0) .done(); } @Test public void update_i_skip_no_actual_update() { init_coih(); // If there is no actual update, all maintenance (twice for each index involving order) should be skipped updateRow(row(i, 111L, 11L, "1234", 111000L), row(i, 111L, 11L, "1234", 111000L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(4) .done(); } @Test public void update_i_skip_update_unindexed() { init_coih(); // There are 2 indexes involving the item table and each may be updated twice. // If extra is updated, which is not involved in any index, then the expected number // of GI maintenance actions skipped is 4. updateRow(row(i, 111L, 11L, "1234", 111000L), row(i, 111L, 11L, "1234", 111111L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(4) .done(); } @Test public void update_h_skip_not_possible_1() { init_coih(); // Update handling_instructions, which is involved in all indexes on item. updateRow(row(h, 1111L, 111L, "don't drop", 1111000L), row(h, 1111L, 111L, "lemon drop", 1111000L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, lemon drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, lemon drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(0) .done(); } @Test public void update_h_skip_not_possible_2() { init_coih(); // Updating both handling instructions and extra -- same number of GI maintenance actions as for updating // handling instructions. updateRow(row(h, 1111L, 111L, "don't drop", 1111000L), row(h, 1111L, 111L, "lemon drop", 1111111L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, lemon drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, lemon drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(0) .done(); } @Test public void update_h_skip_no_actual_update() { init_coih(); // If there is no actual update, all maintenance (twice for each index involving order) should be skipped updateRow(row(h, 1111L, 111L, "don't drop", 1111000L), row(h, 1111L, 111L, "don't drop", 1111000L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(4) .done(); } @Test public void update_h_skip_update_unindexed() { init_coih(); // There are 2 indexes involving the handling table and each may be updated twice. // If extra is updated, which is not involved in any index, then the expected number // of GI maintenance actions skipped is 4. updateRow(row(h, 1111L, 111L, "don't drop", 1111000L), row(h, 1111L, 111L, "don't drop", 1111111L)); checker() .gi(___LEFT_name_when___________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___LEFT_sku_instructions____) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_name_when__________) .entry("John, 2001-01-01, 1, 11").backedBy(c, o) .gi(___RIGHT_sku_instructions___) .entry("1234, don't drop, 1, 11, 111, 1111").backedBy(i, h) .gi(___RIGHT_street_name________) .checkMaintenanceSkips(4) .done(); } @Rule public TestName name = new TestName(); @BeforeClass public static void tapsDefaultToOn() { Tap.defaultToOn(true); } @AfterClass public static void tapsDefaultToOff() { Tap.defaultToOn(false); } @Before public final void createTables() { c = createTable(SCHEMA, "c", "cid int not null primary key, name varchar(32), c_extra int"); o = createTable(SCHEMA, "o", "oid int not null primary key, c_id int, when varchar(32), o_extra int", akibanFK("c_id", "c", "cid") ); i = createTable(SCHEMA, "i", "iid int not null primary key, o_id int, sku int, i_extra int", akibanFK("o_id", "o", "oid") ); h = createTable(SCHEMA, "h", "hid int not null primary key, i_id int, handling_instructions varchar(32), s_extra int", akibanFK("i_id", "i", "iid") ); a = createTable(SCHEMA, "a", "aid int not null primary key, c_id int, street varchar(56), a_extra int", akibanFK("c_id", "c", "cid") ); TableName groupName = group().getName(); Tap.setEnabled(TAP_PATTERN, false); createLeftGroupIndex(groupName, ___LEFT_name_when___________, "c.name", "o.when"); createLeftGroupIndex(groupName, ___LEFT_sku_instructions____, "i.sku", "h.handling_instructions"); createRightGroupIndex(groupName, ___RIGHT_name_when__________, "c.name", "o.when"); createRightGroupIndex(groupName, ___RIGHT_sku_instructions___, "i.sku", "h.handling_instructions"); createRightGroupIndex(groupName, ___RIGHT_street_name________, "a.street", "c.name"); Tap.setEnabled(TAP_PATTERN, true); Tap.reset(TAP_PATTERN); } @After public final void forgetTables() { if (needTapHeaders) { log(TAP_HEADER + "name\t" + Strings.join(reportsByName().keySet(), "\t") ); needTapHeaders = false; } log(TAP_HEADER + name.getMethodName() + "\t" + Strings.join(reportsByName().values(), "\t") ); Tap.setEnabled(TAP_PATTERN, false); int[] ids = { a, h, i, o, c}; int idIndex = 0; for(int i = 5; i >= 0; --i) { try { while(idIndex < ids.length) { dml().truncateTable(session(), ids[idIndex]); ++idIndex; } break; } catch(Exception e) { if(!Exceptions.isRollbackException(e) || i == 0) { throw e; } } } GisCheckBuilder emptyCheckBuilder = checker(); for (GroupIndex gi : group().getIndexes()) { emptyCheckBuilder.gi(gi.getIndexName().getName()); } emptyCheckBuilder.done(); c = null; o = null; i = null; h = null; a = null; assertEquals(Collections.<GisCheckBuilder>emptySet(), unfinishedCheckBuilders); } private static Map<String, Long> reportsByName() { TapReport[] reports = Tap.getReport(TAP_PATTERN); Map<String,Long> reportsByName = new TreeMap<>(); Pattern pattern = Pattern.compile(TAP_PATTERN); for (TapReport report : reports) { Matcher matcher = pattern.matcher(report.getName()); if (!matcher.matches()) throw new RuntimeException("pattern not matched: " + report.getName()); String matched = matcher.group(1); if (matched == null) { matched = matcher.group(2); } assert matched != null; reportsByName.put(matched + " in", report.getInCount()); reportsByName.put(matched + " out", report.getOutCount()); } return reportsByName; } private static void log(String string) { log.debug(string); } private GisCheckBuilder checker() { StackTraceElement callerFrame = Thread.currentThread().getStackTrace()[2]; GisCheckBuilder checkBuilder = new GiCheckBuilderImpl(callerFrame); boolean added = unfinishedCheckBuilders.add(checkBuilder); assert added : unfinishedCheckBuilders; return checkBuilder; } private Group group() { return getTable(c).getGroup(); } private Integer c; private Integer o; private Integer i; private Integer h; private Integer a; private final Set<GisCheckBuilder> unfinishedCheckBuilders = new HashSet<>(); private static final String SCHEMA = "coia"; private static final String ___LEFT_name_when___________ = "name_when_LEFT"; private static final String ___LEFT_sku_instructions____ = "sku_instructions_LEFT"; private static final String ___RIGHT_name_when__________ = "name_when_RIGHT"; private static final String ___RIGHT_sku_instructions___ = "sku_instructions_RIGHT"; private static final String ___RIGHT_street_name________ = "street_name_RIGHT"; private static final String TAP_PATTERN = "GI maintenance: (.+)|.+(skip_gi_maintenance)"; private static final String TAP_HEADER = "GI TAPS:\t"; private static boolean needTapHeaders = true; private static final Logger log = LoggerFactory.getLogger(NewGiUpdateIT.class); // nested classes private interface GisChecker { public void check(); } private interface GisCheckBuilder { GiEntryChecker gi(String giName); public GisCheckBuilder checkMaintenanceSkips(int skipMaintenance); public GisChecker done(); } private interface GiEntryChecker extends GisCheckBuilder { public GiTablesChecker entry(String key); } private interface GiTablesChecker { GiEntryChecker backedBy(int firstTableId, int... tableIds); } private class GiCheckBuilderImpl implements GiEntryChecker, GiTablesChecker { @Override public GiEntryChecker gi(String giName) { assertEquals("", scratch.toString()); giToCheck = group().getIndex(giName); assertNotNull("no GI named " + giName, giToCheck); List<String> oldList = expectedStrings.put(giToCheck, new ArrayList<String>()); assertEquals(null, oldList); return this; } @Override public GiTablesChecker entry(String key) { assertEquals("", scratch.toString()); scratch.append('[').append(key).append("] => "); return this; } @Override public GiEntryChecker backedBy(int firstTableId, int... tableIds) { String scratchString = scratch.toString(); assertTrue(scratchString, scratchString.endsWith(" => ")); assertNotNull(giToCheck); Set<Table> containingTables = new HashSet<>(); AkibanInformationSchema ais = ddl().getAIS(session()); containingTables.add(ais.getTable(firstTableId)); for (int tableId : tableIds) { containingTables.add(ais.getTable(tableId)); } long result = 0; for(Table table = giToCheck.leafMostTable(); table != giToCheck.rootMostTable().getParentTable(); table = table.getParentTable()) { if (containingTables.remove(table)) { result |= 1 << table.getDepth(); } } if (!containingTables.isEmpty()) throw new RuntimeException("tables specified not in the branch: " + containingTables); assert Long.bitCount(result) == tableIds.length + 1; String valueAsString = Long.toBinaryString(result) + " (Long)"; expectedStrings.get(giToCheck).add(scratch.append(valueAsString).toString()); scratch.setLength(0); return this; } @Override public GisCheckBuilder checkMaintenanceSkips(int expectedSkipMaintenance) { Map<String, Long> reports = reportsByName(); long actualSkipMaintenance = reports.get("skip_gi_maintenance in"); assertEquals(expectedSkipMaintenance, actualSkipMaintenance); return this; } @Override public GisChecker done() { unfinishedCheckBuilders.remove(this); GisChecker checker = new GisCheckerImpl(expectedStrings); checker.check(); Tap.reset(TAP_PATTERN); Map<String, Long> reports = reportsByName(); return checker; } @Override public String toString() { return String.format("%s at line %d", frame.getMethodName(), frame.getLineNumber()); } private GiCheckBuilderImpl(StackTraceElement frame) { this.frame = frame; this.scratch = new StringBuilder(); this.expectedStrings = new HashMap<>(); } private final StackTraceElement frame; private final Map<GroupIndex,List<String>> expectedStrings; private GroupIndex giToCheck; private final StringBuilder scratch; } private class GisCheckerImpl implements GisChecker { @Override public void check() { Collection<GroupIndex> gis = group().getIndexes(); Set<GroupIndex> uncheckedGis = new HashSet<>(gis); if (gis.size() != uncheckedGis.size()) fail(gis + ".size() != " + uncheckedGis + ".size()"); for(Map.Entry<GroupIndex,List<String>> checkPair : expectedStrings.entrySet()) { GroupIndex gi = checkPair.getKey(); List<String> expected = checkPair.getValue(); checkIndex(gi, expected); uncheckedGis.remove(gi); } if (!uncheckedGis.isEmpty()) { List<String> uncheckedGiNames = new ArrayList<>(); for (GroupIndex gi : uncheckedGis) { uncheckedGiNames.add(gi.getIndexName().getName()); } throw new RuntimeException("unchecked GIs: " + uncheckedGiNames.toString()); } } private void checkIndex(final GroupIndex groupIndex, final List<String> expected) { transactionallyUnchecked(new Runnable() { @Override public void run() { StringsIndexScanner scanner = store().traverse(session(), groupIndex, new StringsIndexScanner(), -1, 0); AssertUtils.assertCollectionEquals( "scan of " + groupIndex.getIndexName().getName(), expected, scanner.strings() ); } }); } private GisCheckerImpl(Map<GroupIndex, List<String>> expectedStrings) { this.expectedStrings = new HashMap<>(expectedStrings); } private final Map<GroupIndex,List<String>> expectedStrings; } private static class StringsIndexScanner extends IndexRecordVisitor { // IndexVisitor interface @Override public boolean groupIndex() { return true; } // IndexRecordVisitor interface @Override public void visit(List<?> key, Object value) { final String asString; if (value == null) { asString = String.format("%s => null", key); } else { final String className; if (value instanceof Long) { value = Long.toBinaryString((Long)value); className = "Long"; } else { className = value.getClass().getSimpleName(); } asString = String.format("%s => %s (%s)", key, value, className); } _strings.add(asString); } // StringsIndexScanner interface public List<String> strings() { return _strings; } // object state private final List<String> _strings = new ArrayList<>(); } }