/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package org.voltdb.catalog; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.File; import java.util.UUID; import org.junit.Test; import org.voltdb.compiler.VoltCompiler; import org.voltdb.compiler.VoltProjectBuilder; import org.voltdb.compiler.deploymentfile.DrRoleType; import org.voltdb.utils.CatalogUtil; import org.voltdb.utils.Encoder; import org.voltdb.utils.MiscUtils; public class TestDRCatalogDiffs { @Test public void testHappyPath() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER, C3 VARCHAR(50) NOT NULL, " + "PRIMARY KEY (C1), LIMIT PARTITION ROWS 100 EXECUTE (DELETE FROM T1 WHERE C1 < 50));\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "CREATE UNIQUE INDEX foo ON T1 (C3, C1);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER, C3 VARCHAR(50) NOT NULL, " + "PRIMARY KEY (C1), LIMIT PARTITION ROWS 100 EXECUTE (DELETE FROM T1 WHERE C1 < 50));\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "CREATE UNIQUE INDEX foo ON T1 (C3, C1);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testMissingDRTableOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;\n" + "DR TABLE T2;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("Missing DR table T2 on replica cluster")); } @Test public void testTableNotDROnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;\n" + "DR TABLE T2;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T2;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("Table T1 has DR enabled on the master")); } @Test public void testMissingDRTableOnMaster() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;\n" + "DR TABLE T2;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testTableNotDROnMaster() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T2;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;\n" + "DR TABLE T2;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testExtraColumnOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("Missing Column{C2} from Table{T1} on master")); } @Test public void testMissingColumnOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("Missing Column{C2} from Table{T1} on replica")); } @Test public void testMissingColumnOnNonDRTable() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testAddedPartitionColumnOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("field isreplicated in schema object Table{T1}")); } @Test public void testMissingPartitionColumnOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("field isreplicated in schema object Table{T1}")); } @Test public void testDifferentPartitionColumn() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "PARTITION TABLE T1 ON COLUMN C2;\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("field partitioncolumn in schema object Table{T1}")); } @Test public void testExtraRowLimitOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100);\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testMissingRowLimitOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100);\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testDifferentRowLimit() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 50);\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100);\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testAddedDeletePolicyOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100);\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100 EXECUTE (DELETE FROM T1 WHERE C1 > 50));\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testMissingDeletePolicyOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100 EXECUTE (DELETE FROM T1 WHERE C1 > 50));\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100);\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testDifferentDeletePolicy() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100 EXECUTE (DELETE FROM T1 WHERE C1 > 50));\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100 EXECUTE (DELETE FROM T1 WHERE C2 > 50));\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testDeletePolicyWithDifferentWhereClauseConstant() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100 EXECUTE (DELETE FROM T1 WHERE C1 > 50));\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100 EXECUTE (DELETE FROM T1 WHERE C1 > 100));\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testDeletePolicyWithDifferentWhereClauseOperator() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100 EXECUTE (DELETE FROM T1 WHERE C1 > 50));\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100 EXECUTE (DELETE FROM T1 WHERE C1 = 50));\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testDifferentRowLimitWithSameDeletePolicy() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 100 EXECUTE (DELETE FROM T1 WHERE C1 > 50));\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL, LIMIT PARTITION ROWS 50 EXECUTE (DELETE FROM T1 WHERE C1 > 50));\n" + "PARTITION TABLE T1 ON COLUMN C1;\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testDifferentColumnOrder() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C2 INTEGER NOT NULL, C1 INTEGER NOT NULL);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("field index in schema object Column{C1}")); } @Test public void testDifferentColumnType() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 TIMESTAMP NOT NULL);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("field type in schema object Column{C2}")); } @Test public void testDifferentVarcharColumnSize() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50) NOT NULL);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(100) NOT NULL);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("field size in schema object Column{C2}")); } @Test public void testNullableOnlyOnMaster() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50) NOT NULL);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("field nullable in schema object Column{C2}")); } @Test public void testNullableOnlyOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50) NOT NULL);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("field nullable in schema object Column{C2}")); } @Test public void testExtraUniqueIndexOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "CREATE UNIQUE INDEX foo ON T1 (C1);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("Missing Index{FOO} from Table{T1} on master")); } @Test public void testMissingUniqueIndexOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "CREATE UNIQUE INDEX foo ON T1 (C1);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("Missing Index{FOO} from Table{T1} on replica")); } @Test public void testReorderUniqueIndexOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "CREATE UNIQUE INDEX foo ON T1 (C1, C2);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "CREATE UNIQUE INDEX foo ON T1 (C2, C1);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("field index in schema object ColumnRef{C1}")); } @Test public void testIndexOnlyUniqueOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "CREATE INDEX foo ON T1 (C1, C2);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "CREATE UNIQUE INDEX foo ON T1 (C1, C2);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("field unique in schema object Index{FOO}")); } @Test public void testIndexOnlyUniqueOnMaster() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "CREATE UNIQUE INDEX foo ON T1 (C1, C2);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "CREATE INDEX foo ON T1 (C1, C2);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("field unique in schema object Index{FOO}")); } @Test public void testUniqueIndexOnDifferentTable() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50) NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 VARCHAR(50) NOT NULL);\n" + "CREATE UNIQUE INDEX foo ON T1 (C1, C2);\n" + "DR TABLE T1;\n" + "DR TABLE T2;\n"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50) NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 VARCHAR(50) NOT NULL);\n" + "CREATE UNIQUE INDEX foo ON T2 (C1, C2);\n" + "DR TABLE T1;\n" + "DR TABLE T2;\n"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("Missing Index{FOO} from Table{T1} on replica")); assertTrue(diff.errors().contains("Missing Index{FOO} from Table{T2} on master")); } @Test public void testExtraPrimaryKeyOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50), PRIMARY KEY (C1));\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("Missing Index{VOLTDB_AUTOGEN_IDX_PK_T1} from Table{T1} on master")); } @Test public void testMissingPrimaryKeyOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50), PRIMARY KEY (C1));\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("Missing Index{VOLTDB_AUTOGEN_IDX_PK_T1} from Table{T1} on replica")); } @Test public void testExtraColumnInPrimaryKeyOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50) NOT NULL, PRIMARY KEY (C1));\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50) NOT NULL, PRIMARY KEY (C1, C2));\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("Missing ColumnRef{C2} from Index{VOLTDB_AUTOGEN_IDX_PK_T1} on master")); } @Test public void testMissingColumnInPrimaryKeyOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50) NOT NULL, PRIMARY KEY (C1, C2));\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50) NOT NULL, PRIMARY KEY (C1));\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("Missing ColumnRef{C2} from Index{VOLTDB_AUTOGEN_IDX_PK_T1} on replica")); } @Test public void testReorderPrimaryKeyOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50) NOT NULL, PRIMARY KEY (C1, C2));\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50) NOT NULL, PRIMARY KEY (C2, C1));\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("Incompatible schema between master and replica: field index in schema object ColumnRef{C1}")); assertTrue(diff.errors().contains("Incompatible schema between master and replica: field index in schema object ColumnRef{C2}")); } @Test public void testExtraNonuniqueIndexOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "CREATE INDEX foo ON T1 (C1);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testMissingNonuniqueIndexOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "CREATE INDEX foo ON T1 (C1);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testDifferentNonuniqueIndexOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "CREATE INDEX foo ON T1 (C1, C2);\n" + "DR TABLE T1;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 VARCHAR(50));\n" + "CREATE INDEX foo ON T1 (C2, C1);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testExtraViewOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;\n" + "DR TABLE T2;\n"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE VIEW foo (C1, total) AS SELECT C1, COUNT(*) FROM T1 GROUP BY C1;\n" + "CREATE VIEW foo2 (C1, total) AS SELECT T1.C1, COUNT(*) FROM T1 JOIN T2 ON T1.C1 = T2.C1 GROUP BY T1.C1;\n" + "DR TABLE T1;\n" + "DR TABLE T2;\n"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testMissingViewOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE VIEW foo (C1, total) AS SELECT C1, COUNT(*) FROM T1 GROUP BY C1;\n" + "CREATE VIEW foo2 (C1, total) AS SELECT T1.C1, COUNT(*) FROM T1 JOIN T2 ON T1.C1 = T2.C1 GROUP BY T1.C1;\n" + "DR TABLE T1;\n" + "DR TABLE T2;\n"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;\n" + "DR TABLE T2;\n"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testDifferentViewOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE VIEW foo (C1, total) AS SELECT C1, COUNT(*) FROM T1 GROUP BY C1;\n" + "CREATE VIEW foo2 (C1, total) AS SELECT T1.C1, COUNT(*) FROM T1 JOIN T2 ON T1.C1 = T2.C1 GROUP BY T1.C1;\n" + "DR TABLE T1;\n" + "DR TABLE T2;"; String replicaSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE VIEW foo (C1, C2, total) AS SELECT C1, C2, COUNT(*) FROM T1 WHERE C2 > 50 GROUP BY C1, C2;\n" + "CREATE VIEW foo2 (C1, C2, total) AS SELECT T1.C1, T2.C2, COUNT(*) FROM T1 JOIN T2 ON T1.C1 = T2.C1 GROUP BY T1.C1, T2.C2;\n" + "DR TABLE T1;\n" + "DR TABLE T2;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testNoDRTablesOnMaster() throws Exception { String replicaSchema = "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T2;"; CatalogDiffEngine diff = runCatalogDiff("", false, replicaSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testEmptySchemaOnReplica() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(masterSchema, false, "", false); assertFalse(diff.errors(), diff.supported()); assertTrue(diff.errors().contains("Missing DR table T1 on replica cluster")); } @Test public void testNoDRTablesOnEitherSide() throws Exception { CatalogDiffEngine diff = runCatalogDiff("", false, "", false); assertTrue(diff.errors(), diff.supported()); } @Test public void testActiveActiveDR() throws Exception { String nodeOneSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; String nodeTwoSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(nodeOneSchema, true, nodeTwoSchema, true); assertTrue(diff.errors(), diff.supported()); } @Test public void testActivePassiveDR() throws Exception { String nodeOneSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; String nodeTwoSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(nodeOneSchema, false, nodeTwoSchema, false); assertTrue(diff.errors(), diff.supported()); } @Test public void testIncompatibleDRMode() throws Exception { String nodeOneSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; String nodeTwoSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "DR TABLE T1;"; CatalogDiffEngine diff = runCatalogDiff(nodeOneSchema, true, nodeTwoSchema, false); assertFalse(diff.supported()); assertTrue(diff.errors().contains("Incompatible DR modes between two clusters")); } @Test public void testUnknownSchemaOption() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 BIGINT NOT NULL);\n" + "DR TABLE T1;"; Catalog masterCatalog = createCatalog(masterSchema); String commands = DRCatalogDiffEngine.serializeCatalogCommandsForDr(masterCatalog, -1).commands; String decodedCommands = Encoder.decodeBase64AndDecompress(commands); decodedCommands = decodedCommands.replaceFirst("set \\$PREV isDRed true", "set \\$PREV isDRed true\nset \\$PREV isASquirrel false"); boolean threw = false; try { DRCatalogDiffEngine.deserializeCatalogCommandsForDr(Encoder.compressAndBase64Encode(decodedCommands)); } catch (Exception e) { assertTrue(e.getMessage().contains("$PREV isASquirrel false")); threw = true; } assertTrue(threw); } /** * Don't serialize views, DR doesn't care. */ @Test public void testFilterForDR() throws Exception { String masterSchema = "CREATE TABLE T1 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE TABLE T2 (C1 INTEGER NOT NULL, C2 INTEGER NOT NULL);\n" + "CREATE INDEX T1_IDX ON T1 (C2);\n" + "CREATE VIEW foo (C1, total) AS SELECT C1, COUNT(*) FROM T1 GROUP BY C1;\n" + "CREATE VIEW foo2 (C1, total) AS SELECT T1.C1, COUNT(*) FROM T1 JOIN T2 ON T1.C1 = T2.C1 GROUP BY T1.C1;\n" + "DR TABLE T1;\n" + "DR TABLE T2;\n"; Catalog masterCatalog = createCatalog(masterSchema); String commands = DRCatalogDiffEngine.serializeCatalogCommandsForDr(masterCatalog, -1).commands; String decodedCommands = Encoder.decodeBase64AndDecompress(commands); assertFalse(decodedCommands.contains(" views ")); assertFalse(decodedCommands.contains(" mvHandlerInfo ")); assertFalse(decodedCommands.contains(" isSafeWithNonemptySources ")); } private CatalogDiffEngine runCatalogDiff(String masterSchema, boolean isMasterXDCR, String replicaSchema, boolean isReplicaXDCR) throws Exception { Catalog masterCatalog = createCatalog(masterSchema); if (isMasterXDCR) { masterCatalog.getClusters().get("cluster").setDrrole(DrRoleType.XDCR.value()); } Catalog replicaCatalog = createCatalog(replicaSchema); if (isReplicaXDCR) { replicaCatalog.getClusters().get("cluster").setDrrole(DrRoleType.XDCR.value()); } String commands = DRCatalogDiffEngine.serializeCatalogCommandsForDr(masterCatalog, -1).commands; Catalog deserializedMasterCatalog = DRCatalogDiffEngine.deserializeCatalogCommandsForDr(commands); return new DRCatalogDiffEngine(replicaCatalog, deserializedMasterCatalog); } private Catalog createCatalog(String schema) throws Exception { File jarOut = new File(UUID.randomUUID() + ".jar"); jarOut.deleteOnExit(); File schemaFile = VoltProjectBuilder.writeStringToTempFile(schema); String schemaPath = schemaFile.getPath(); VoltCompiler compiler = new VoltCompiler(false); boolean success = compiler.compileFromDDL(jarOut.getPath(), schemaPath); assertTrue("Compilation failed unexpectedly", success); Catalog catalog = new Catalog(); catalog.execute(CatalogUtil.getSerializedCatalogStringFromJar(CatalogUtil.loadAndUpgradeCatalogFromJar(MiscUtils.fileToBytes(new File(jarOut.getPath())), false).getFirst())); return catalog; } }