github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/changefeedccl/cdctest/validator_test.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Licensed as a CockroachDB Enterprise file under the Cockroach Community
     4  // License (the "License"); you may not use this file except in compliance with
     5  // the License. You may obtain a copy of the License at
     6  //
     7  //     https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
     8  
     9  package cdctest
    10  
    11  import (
    12  	"context"
    13  	"fmt"
    14  	"reflect"
    15  	"testing"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/base"
    18  	"github.com/cockroachdb/cockroach/pkg/sql"
    19  	"github.com/cockroachdb/cockroach/pkg/testutils"
    20  	"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
    21  	"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
    22  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    23  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  func ts(i int64) hlc.Timestamp {
    28  	return hlc.Timestamp{WallTime: i}
    29  }
    30  
    31  func noteRow(t *testing.T, v Validator, partition, key, value string, updated hlc.Timestamp) {
    32  	t.Helper()
    33  	if err := v.NoteRow(partition, key, value, updated); err != nil {
    34  		t.Fatal(err)
    35  	}
    36  }
    37  
    38  func noteResolved(t *testing.T, v Validator, partition string, resolved hlc.Timestamp) {
    39  	t.Helper()
    40  	if err := v.NoteResolved(partition, resolved); err != nil {
    41  		t.Fatal(err)
    42  	}
    43  }
    44  
    45  func assertValidatorFailures(t *testing.T, v Validator, expected ...string) {
    46  	t.Helper()
    47  	if f := v.Failures(); !reflect.DeepEqual(f, expected) {
    48  		t.Errorf(`got %v expected %v`, f, expected)
    49  	}
    50  }
    51  
    52  func TestOrderValidator(t *testing.T) {
    53  	defer leaktest.AfterTest(t)()
    54  	const ignored = `ignored`
    55  
    56  	t.Run(`empty`, func(t *testing.T) {
    57  		v := NewOrderValidator(`t1`)
    58  		if f := v.Failures(); f != nil {
    59  			t.Fatalf("got %v expected %v", f, nil)
    60  		}
    61  	})
    62  	t.Run(`dupe okay`, func(t *testing.T) {
    63  		v := NewOrderValidator(`t1`)
    64  		noteRow(t, v, `p1`, `k1`, ignored, ts(1))
    65  		noteRow(t, v, `p1`, `k1`, ignored, ts(2))
    66  		noteRow(t, v, `p1`, `k1`, ignored, ts(1))
    67  		assertValidatorFailures(t, v)
    68  	})
    69  	t.Run(`key on two partitions`, func(t *testing.T) {
    70  		v := NewOrderValidator(`t1`)
    71  		noteRow(t, v, `p1`, `k1`, ignored, ts(2))
    72  		noteRow(t, v, `p2`, `k1`, ignored, ts(1))
    73  		assertValidatorFailures(t, v,
    74  			`key [k1] received on two partitions: p1 and p2`,
    75  		)
    76  	})
    77  	t.Run(`new key with lower timestamp`, func(t *testing.T) {
    78  		v := NewOrderValidator(`t1`)
    79  		noteRow(t, v, `p1`, `k1`, ignored, ts(2))
    80  		noteRow(t, v, `p1`, `k1`, ignored, ts(1))
    81  		assertValidatorFailures(t, v,
    82  			`topic t1 partition p1: saw new row timestamp 1.0000000000 after 2.0000000000 was seen`,
    83  		)
    84  	})
    85  	t.Run(`new key after resolved`, func(t *testing.T) {
    86  		v := NewOrderValidator(`t1`)
    87  		noteResolved(t, v, `p2`, ts(3))
    88  		// Okay because p2 saw the resolved timestamp but p1 didn't.
    89  		noteRow(t, v, `p1`, `k1`, ignored, ts(1))
    90  		noteResolved(t, v, `p1`, ts(3))
    91  		// This one is not okay.
    92  		noteRow(t, v, `p1`, `k1`, ignored, ts(2))
    93  		// Still okay because we've seen it before.
    94  		noteRow(t, v, `p1`, `k1`, ignored, ts(1))
    95  		assertValidatorFailures(t, v,
    96  			`topic t1 partition p1`+
    97  				`: saw new row timestamp 2.0000000000 after 3.0000000000 was resolved`,
    98  		)
    99  	})
   100  }
   101  
   102  func TestBeforeAfterValidator(t *testing.T) {
   103  	defer leaktest.AfterTest(t)()
   104  
   105  	ctx := context.Background()
   106  	s, sqlDBRaw, _ := serverutils.StartServer(t, base.TestServerArgs{UseDatabase: "d"})
   107  	defer s.Stopper().Stop(ctx)
   108  	sqlDB := sqlutils.MakeSQLRunner(sqlDBRaw)
   109  	sqlDB.Exec(t, `CREATE DATABASE d`)
   110  	sqlDB.Exec(t, `CREATE TABLE foo (k INT PRIMARY KEY, v INT)`)
   111  
   112  	tsRaw := make([]string, 6)
   113  	sqlDB.QueryRow(t, `SELECT cluster_logical_timestamp()`).Scan(&tsRaw[0])
   114  	sqlDB.QueryRow(t,
   115  		`UPSERT INTO foo VALUES (1, 1) RETURNING cluster_logical_timestamp()`,
   116  	).Scan(&tsRaw[1])
   117  	sqlDB.QueryRow(t,
   118  		`UPSERT INTO foo VALUES (1, 2), (2, 2) RETURNING cluster_logical_timestamp()`,
   119  	).Scan(&tsRaw[2])
   120  	sqlDB.QueryRow(t,
   121  		`UPSERT INTO foo VALUES (1, 3) RETURNING cluster_logical_timestamp()`,
   122  	).Scan(&tsRaw[3])
   123  	sqlDB.QueryRow(t,
   124  		`DELETE FROM foo WHERE k = 1 RETURNING cluster_logical_timestamp()`,
   125  	).Scan(&tsRaw[4])
   126  	sqlDB.QueryRow(t, `SELECT cluster_logical_timestamp()`).Scan(&tsRaw[5])
   127  	ts := make([]hlc.Timestamp, len(tsRaw))
   128  	for i := range tsRaw {
   129  		var err error
   130  		ts[i], err = sql.ParseHLC(tsRaw[i])
   131  		if err != nil {
   132  			t.Fatal(err)
   133  		}
   134  	}
   135  
   136  	t.Run(`empty`, func(t *testing.T) {
   137  		v, err := NewBeforeAfterValidator(sqlDBRaw, `foo`)
   138  		require.NoError(t, err)
   139  		assertValidatorFailures(t, v)
   140  	})
   141  	t.Run(`during initial`, func(t *testing.T) {
   142  		v, err := NewBeforeAfterValidator(sqlDBRaw, `foo`)
   143  		require.NoError(t, err)
   144  		// "before" is ignored if missing.
   145  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":1}}`, ts[1])
   146  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":2}}`, ts[2])
   147  		// However, if provided, it is validated.
   148  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":2}, "before": {"k":1,"v":1}}`, ts[2])
   149  		assertValidatorFailures(t, v)
   150  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":3}, "before": {"k":1,"v":3}}`, ts[3])
   151  		assertValidatorFailures(t, v,
   152  			`"before" field did not agree with row at `+ts[3].Prev().AsOfSystemTime()+
   153  				`: SELECT count(*) = 1 FROM foo AS OF SYSTEM TIME '`+ts[3].Prev().AsOfSystemTime()+
   154  				`' WHERE k = $1 AND v = $2 [1 3]`)
   155  	})
   156  	t.Run(`missing before`, func(t *testing.T) {
   157  		v, err := NewBeforeAfterValidator(sqlDBRaw, `foo`)
   158  		require.NoError(t, err)
   159  		noteResolved(t, v, `p`, ts[0])
   160  		// "before" should have been provided.
   161  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":2}}`, ts[2])
   162  		assertValidatorFailures(t, v,
   163  			`"before" field did not agree with row at `+ts[2].Prev().AsOfSystemTime()+
   164  				`: SELECT count(*) = 0 FROM foo AS OF SYSTEM TIME '`+ts[2].Prev().AsOfSystemTime()+
   165  				`' WHERE k = $1 [1]`)
   166  	})
   167  	t.Run(`incorrect before`, func(t *testing.T) {
   168  		v, err := NewBeforeAfterValidator(sqlDBRaw, `foo`)
   169  		require.NoError(t, err)
   170  		noteResolved(t, v, `p`, ts[0])
   171  		// "before" provided with wrong value.
   172  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":3}, "before": {"k":5,"v":10}}`, ts[3])
   173  		assertValidatorFailures(t, v,
   174  			`"before" field did not agree with row at `+ts[3].Prev().AsOfSystemTime()+
   175  				`: SELECT count(*) = 1 FROM foo AS OF SYSTEM TIME '`+ts[3].Prev().AsOfSystemTime()+
   176  				`' WHERE k = $1 AND v = $2 [5 10]`)
   177  	})
   178  	t.Run(`unnecessary before`, func(t *testing.T) {
   179  		v, err := NewBeforeAfterValidator(sqlDBRaw, `foo`)
   180  		require.NoError(t, err)
   181  		noteResolved(t, v, `p`, ts[0])
   182  		// "before" provided but should not have been.
   183  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":1}, "before": {"k":1,"v":1}}`, ts[1])
   184  		assertValidatorFailures(t, v,
   185  			`"before" field did not agree with row at `+ts[1].Prev().AsOfSystemTime()+
   186  				`: SELECT count(*) = 1 FROM foo AS OF SYSTEM TIME '`+ts[1].Prev().AsOfSystemTime()+
   187  				`' WHERE k = $1 AND v = $2 [1 1]`)
   188  	})
   189  	t.Run(`missing after`, func(t *testing.T) {
   190  		v, err := NewBeforeAfterValidator(sqlDBRaw, `foo`)
   191  		require.NoError(t, err)
   192  		noteResolved(t, v, `p`, ts[0])
   193  		// "after" should have been provided.
   194  		noteRow(t, v, `p`, `[1]`, `{"before": {"k":1,"v":1}}`, ts[2])
   195  		assertValidatorFailures(t, v,
   196  			`"after" field did not agree with row at `+ts[2].AsOfSystemTime()+
   197  				`: SELECT count(*) = 0 FROM foo AS OF SYSTEM TIME '`+ts[2].AsOfSystemTime()+
   198  				`' WHERE k = $1 [1]`)
   199  	})
   200  	t.Run(`incorrect after`, func(t *testing.T) {
   201  		v, err := NewBeforeAfterValidator(sqlDBRaw, `foo`)
   202  		require.NoError(t, err)
   203  		noteResolved(t, v, `p`, ts[0])
   204  		// "after" provided with wrong value.
   205  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":5}, "before": {"k":1,"v":2}}`, ts[3])
   206  		assertValidatorFailures(t, v,
   207  			`"after" field did not agree with row at `+ts[3].AsOfSystemTime()+
   208  				`: SELECT count(*) = 1 FROM foo AS OF SYSTEM TIME '`+ts[3].AsOfSystemTime()+
   209  				`' WHERE k = $1 AND v = $2 [1 5]`)
   210  	})
   211  	t.Run(`unnecessary after`, func(t *testing.T) {
   212  		v, err := NewBeforeAfterValidator(sqlDBRaw, `foo`)
   213  		require.NoError(t, err)
   214  		noteResolved(t, v, `p`, ts[0])
   215  		// "after" provided but should not have been.
   216  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":3}, "before": {"k":1,"v":3}}`, ts[4])
   217  		assertValidatorFailures(t, v,
   218  			`"after" field did not agree with row at `+ts[4].AsOfSystemTime()+
   219  				`: SELECT count(*) = 1 FROM foo AS OF SYSTEM TIME '`+ts[4].AsOfSystemTime()+
   220  				`' WHERE k = $1 AND v = $2 [1 3]`)
   221  	})
   222  	t.Run(`incorrect before and after`, func(t *testing.T) {
   223  		v, err := NewBeforeAfterValidator(sqlDBRaw, `foo`)
   224  		require.NoError(t, err)
   225  		noteResolved(t, v, `p`, ts[0])
   226  		// "before" and "after" both provided with wrong value.
   227  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":5}, "before": {"k":1,"v":4}}`, ts[3])
   228  		assertValidatorFailures(t, v,
   229  			`"after" field did not agree with row at `+ts[3].AsOfSystemTime()+
   230  				`: SELECT count(*) = 1 FROM foo AS OF SYSTEM TIME '`+ts[3].AsOfSystemTime()+
   231  				`' WHERE k = $1 AND v = $2 [1 5]`,
   232  			`"before" field did not agree with row at `+ts[3].Prev().AsOfSystemTime()+
   233  				`: SELECT count(*) = 1 FROM foo AS OF SYSTEM TIME '`+ts[3].Prev().AsOfSystemTime()+
   234  				`' WHERE k = $1 AND v = $2 [1 4]`)
   235  	})
   236  	t.Run(`correct`, func(t *testing.T) {
   237  		v, err := NewBeforeAfterValidator(sqlDBRaw, `foo`)
   238  		require.NoError(t, err)
   239  		noteResolved(t, v, `p`, ts[0])
   240  		noteRow(t, v, `p`, `[1]`, `{}`, ts[0])
   241  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":1}}`, ts[1])
   242  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":1}, "before": null}`, ts[1])
   243  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":2}, "before": {"k":1,"v":1}}`, ts[2])
   244  		noteRow(t, v, `p`, `[1]`, `{"after": {"k":1,"v":3}, "before": {"k":1,"v":2}}`, ts[3])
   245  		noteRow(t, v, `p`, `[1]`, `{                        "before": {"k":1,"v":3}}`, ts[4])
   246  		noteRow(t, v, `p`, `[1]`, `{"after": null,          "before": {"k":1,"v":3}}`, ts[4])
   247  		noteRow(t, v, `p`, `[2]`, `{}`, ts[1])
   248  		noteRow(t, v, `p`, `[2]`, `{"after": {"k":2,"v":2}}`, ts[2])
   249  		noteRow(t, v, `p`, `[2]`, `{"after": {"k":2,"v":2}, "before": null}`, ts[2])
   250  		assertValidatorFailures(t, v)
   251  	})
   252  }
   253  
   254  func TestFingerprintValidator(t *testing.T) {
   255  	defer leaktest.AfterTest(t)()
   256  	const ignored = `ignored`
   257  
   258  	ctx := context.Background()
   259  	s, sqlDBRaw, _ := serverutils.StartServer(t, base.TestServerArgs{UseDatabase: "d"})
   260  	defer s.Stopper().Stop(ctx)
   261  	sqlDB := sqlutils.MakeSQLRunner(sqlDBRaw)
   262  	sqlDB.Exec(t, `CREATE DATABASE d`)
   263  	sqlDB.Exec(t, `CREATE TABLE foo (k INT PRIMARY KEY, v INT)`)
   264  
   265  	tsRaw := make([]string, 6)
   266  	sqlDB.QueryRow(t, `SELECT cluster_logical_timestamp()`).Scan(&tsRaw[0])
   267  	sqlDB.QueryRow(t,
   268  		`UPSERT INTO foo VALUES (1, 1) RETURNING cluster_logical_timestamp()`,
   269  	).Scan(&tsRaw[1])
   270  	sqlDB.QueryRow(t,
   271  		`UPSERT INTO foo VALUES (1, 2), (2, 2) RETURNING cluster_logical_timestamp()`,
   272  	).Scan(&tsRaw[2])
   273  	sqlDB.QueryRow(t,
   274  		`UPSERT INTO foo VALUES (1, 3) RETURNING cluster_logical_timestamp()`,
   275  	).Scan(&tsRaw[3])
   276  	sqlDB.QueryRow(t,
   277  		`DELETE FROM foo WHERE k = 1 RETURNING cluster_logical_timestamp()`,
   278  	).Scan(&tsRaw[4])
   279  	sqlDB.QueryRow(t, `SELECT cluster_logical_timestamp()`).Scan(&tsRaw[5])
   280  	ts := make([]hlc.Timestamp, len(tsRaw))
   281  	for i := range tsRaw {
   282  		var err error
   283  		ts[i], err = sql.ParseHLC(tsRaw[i])
   284  		if err != nil {
   285  			t.Fatal(err)
   286  		}
   287  	}
   288  
   289  	createTableStmt := func(tableName string) string {
   290  		return fmt.Sprintf(`CREATE TABLE %s (k INT PRIMARY KEY, v INT)`, tableName)
   291  	}
   292  	testColumns := 0
   293  
   294  	t.Run(`empty`, func(t *testing.T) {
   295  		sqlDB.Exec(t, createTableStmt(`empty`))
   296  		v, err := NewFingerprintValidator(sqlDBRaw, `foo`, `empty`, []string{`p`}, testColumns)
   297  		require.NoError(t, err)
   298  		noteResolved(t, v, `p`, ts[0])
   299  		assertValidatorFailures(t, v)
   300  	})
   301  	t.Run(`wrong data`, func(t *testing.T) {
   302  		sqlDB.Exec(t, createTableStmt(`wrong_data`))
   303  		v, err := NewFingerprintValidator(sqlDBRaw, `foo`, `wrong_data`, []string{`p`}, testColumns)
   304  		require.NoError(t, err)
   305  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":10}}`, ts[1])
   306  		noteResolved(t, v, `p`, ts[1])
   307  		assertValidatorFailures(t, v,
   308  			`fingerprints did not match at `+ts[1].AsOfSystemTime()+
   309  				`: 590700560494856539 vs -2774220564100127343`,
   310  		)
   311  	})
   312  	t.Run(`all resolved`, func(t *testing.T) {
   313  		sqlDB.Exec(t, createTableStmt(`all_resolved`))
   314  		v, err := NewFingerprintValidator(sqlDBRaw, `foo`, `all_resolved`, []string{`p`}, testColumns)
   315  		require.NoError(t, err)
   316  		if err := v.NoteResolved(`p`, ts[0]); err != nil {
   317  			t.Fatal(err)
   318  		}
   319  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":1}}`, ts[1])
   320  		noteResolved(t, v, `p`, ts[1])
   321  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":2}}`, ts[2])
   322  		noteRow(t, v, ignored, `[2]`, `{"after": {"k":2,"v":2}}`, ts[2])
   323  		noteResolved(t, v, `p`, ts[2])
   324  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":3}}`, ts[3])
   325  		noteResolved(t, v, `p`, ts[3])
   326  		noteRow(t, v, ignored, `[1]`, `{"after": null}`, ts[4])
   327  		noteResolved(t, v, `p`, ts[4])
   328  		noteResolved(t, v, `p`, ts[5])
   329  		assertValidatorFailures(t, v)
   330  	})
   331  	t.Run(`rows unsorted`, func(t *testing.T) {
   332  		sqlDB.Exec(t, createTableStmt(`rows_unsorted`))
   333  		v, err := NewFingerprintValidator(sqlDBRaw, `foo`, `rows_unsorted`, []string{`p`}, testColumns)
   334  		require.NoError(t, err)
   335  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":3}}`, ts[3])
   336  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":2}}`, ts[2])
   337  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":1}}`, ts[1])
   338  		noteRow(t, v, ignored, `[1]`, `{"after": null}`, ts[4])
   339  		noteRow(t, v, ignored, `[2]`, `{"after": {"k":2,"v":2}}`, ts[2])
   340  		noteResolved(t, v, `p`, ts[5])
   341  		assertValidatorFailures(t, v)
   342  	})
   343  	t.Run(`missed initial`, func(t *testing.T) {
   344  		sqlDB.Exec(t, createTableStmt(`missed_initial`))
   345  		v, err := NewFingerprintValidator(sqlDBRaw, `foo`, `missed_initial`, []string{`p`}, testColumns)
   346  		require.NoError(t, err)
   347  		noteResolved(t, v, `p`, ts[0])
   348  		// Intentionally missing {"k":1,"v":1} at ts[1].
   349  		// Insert a fake row since we don't fingerprint earlier than the first seen row.
   350  		noteRow(t, v, ignored, `[2]`, `{"after": {"k":2,"v":2}}`, ts[2].Prev())
   351  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":2}}`, ts[2])
   352  		noteRow(t, v, ignored, `[2]`, `{"after": {"k":2,"v":2}}`, ts[2])
   353  		noteResolved(t, v, `p`, ts[2].Prev())
   354  		assertValidatorFailures(t, v,
   355  			`fingerprints did not match at `+ts[2].Prev().AsOfSystemTime()+
   356  				`: 590700560494856539 vs 590699460983228293`,
   357  		)
   358  	})
   359  	t.Run(`missed middle`, func(t *testing.T) {
   360  		sqlDB.Exec(t, createTableStmt(`missed_middle`))
   361  		v, err := NewFingerprintValidator(sqlDBRaw, `foo`, `missed_middle`, []string{`p`}, testColumns)
   362  		require.NoError(t, err)
   363  		noteResolved(t, v, `p`, ts[0])
   364  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":1}}`, ts[1])
   365  		// Intentionally missing {"k":1,"v":2} at ts[2].
   366  		noteRow(t, v, ignored, `[2]`, `{"after": {"k":2,"v":2}}`, ts[2])
   367  		noteResolved(t, v, `p`, ts[2])
   368  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":3}}`, ts[3])
   369  		noteResolved(t, v, `p`, ts[3])
   370  		assertValidatorFailures(t, v,
   371  			`fingerprints did not match at `+ts[2].AsOfSystemTime()+
   372  				`: 1099511631581 vs 1099511631582`,
   373  			`fingerprints did not match at `+ts[3].Prev().AsOfSystemTime()+
   374  				`: 1099511631581 vs 1099511631582`,
   375  		)
   376  	})
   377  	t.Run(`missed end`, func(t *testing.T) {
   378  		sqlDB.Exec(t, createTableStmt(`missed_end`))
   379  		v, err := NewFingerprintValidator(sqlDBRaw, `foo`, `missed_end`, []string{`p`}, testColumns)
   380  		require.NoError(t, err)
   381  		noteResolved(t, v, `p`, ts[0])
   382  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":1}}`, ts[1])
   383  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":2}}`, ts[2])
   384  		noteRow(t, v, ignored, `[2]`, `{"after": {"k":2,"v":2}}`, ts[2])
   385  		// Intentionally missing {"k":1,"v":3} at ts[3].
   386  		noteResolved(t, v, `p`, ts[3])
   387  		assertValidatorFailures(t, v,
   388  			`fingerprints did not match at `+ts[3].AsOfSystemTime()+
   389  				`: 1099511631580 vs 1099511631581`,
   390  		)
   391  	})
   392  	t.Run(`initial scan`, func(t *testing.T) {
   393  		sqlDB.Exec(t, createTableStmt(`initial_scan`))
   394  		v, err := NewFingerprintValidator(sqlDBRaw, `foo`, `initial_scan`, []string{`p`}, testColumns)
   395  		require.NoError(t, err)
   396  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":3}}`, ts[3])
   397  		noteRow(t, v, ignored, `[2]`, `{"after": {"k":2,"v":2}}`, ts[3])
   398  		noteResolved(t, v, `p`, ts[3])
   399  		assertValidatorFailures(t, v)
   400  	})
   401  	t.Run(`unknown partition`, func(t *testing.T) {
   402  		sqlDB.Exec(t, createTableStmt(`unknown_partition`))
   403  		v, err := NewFingerprintValidator(sqlDBRaw, `foo`, `unknown_partition`, []string{`p`}, testColumns)
   404  		require.NoError(t, err)
   405  		if err := v.NoteResolved(`nope`, ts[1]); !testutils.IsError(err, `unknown partition`) {
   406  			t.Fatalf(`expected "unknown partition" error got: %+v`, err)
   407  		}
   408  	})
   409  	t.Run(`resolved unsorted`, func(t *testing.T) {
   410  		sqlDB.Exec(t, createTableStmt(`resolved_unsorted`))
   411  		v, err := NewFingerprintValidator(sqlDBRaw, `foo`, `resolved_unsorted`, []string{`p`}, testColumns)
   412  		require.NoError(t, err)
   413  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":1}}`, ts[1])
   414  		noteResolved(t, v, `p`, ts[1])
   415  		noteResolved(t, v, `p`, ts[1])
   416  		noteResolved(t, v, `p`, ts[0])
   417  		assertValidatorFailures(t, v)
   418  	})
   419  	t.Run(`two partitions`, func(t *testing.T) {
   420  		sqlDB.Exec(t, createTableStmt(`two_partitions`))
   421  		v, err := NewFingerprintValidator(sqlDBRaw, `foo`, `two_partitions`, []string{`p0`, `p1`}, testColumns)
   422  		require.NoError(t, err)
   423  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":1}}`, ts[1])
   424  		noteRow(t, v, ignored, `[1]`, `{"after": {"k":1,"v":2}}`, ts[2])
   425  		// Intentionally missing {"k":2,"v":2}.
   426  		noteResolved(t, v, `p0`, ts[2])
   427  		noteResolved(t, v, `p0`, ts[4])
   428  		// p1 has not been closed, so no failures yet.
   429  		assertValidatorFailures(t, v)
   430  		noteResolved(t, v, `p1`, ts[2])
   431  		assertValidatorFailures(t, v,
   432  			`fingerprints did not match at `+ts[2].AsOfSystemTime()+
   433  				`: 1099511631581 vs 590700560494856536`,
   434  		)
   435  	})
   436  }
   437  
   438  func TestValidators(t *testing.T) {
   439  	defer leaktest.AfterTest(t)()
   440  	const ignored = `ignored`
   441  
   442  	t.Run(`empty`, func(t *testing.T) {
   443  		v := Validators{
   444  			NewOrderValidator(`t1`),
   445  			NewOrderValidator(`t2`),
   446  		}
   447  		if f := v.Failures(); f != nil {
   448  			t.Fatalf("got %v expected %v", f, nil)
   449  		}
   450  	})
   451  	t.Run(`failures`, func(t *testing.T) {
   452  		v := Validators{
   453  			NewOrderValidator(`t1`),
   454  			NewOrderValidator(`t2`),
   455  		}
   456  		noteResolved(t, v, `p1`, ts(2))
   457  		noteRow(t, v, `p1`, `k1`, ignored, ts(1))
   458  		assertValidatorFailures(t, v,
   459  			`topic t1 partition p1`+
   460  				`: saw new row timestamp 1.0000000000 after 2.0000000000 was resolved`,
   461  			`topic t2 partition p1`+
   462  				`: saw new row timestamp 1.0000000000 after 2.0000000000 was resolved`,
   463  		)
   464  	})
   465  }