From 80db4ab9d3512b745a7888f661c5a80842a26bfb Mon Sep 17 00:00:00 2001 From: lance6716 Date: Tue, 5 Jul 2022 16:27:43 +0800 Subject: [PATCH 1/3] ddl: add a simply store for InfoSchema --- ddl/dm_schema_tracker.go | 150 ++++++++++++++++++++++++++++++++++ ddl/dm_schema_tracker_test.go | 116 ++++++++++++++++++++++++++ 2 files changed, 266 insertions(+) create mode 100644 ddl/dm_schema_tracker.go create mode 100644 ddl/dm_schema_tracker_test.go diff --git a/ddl/dm_schema_tracker.go b/ddl/dm_schema_tracker.go new file mode 100644 index 0000000000000..f5bbb3498b62f --- /dev/null +++ b/ddl/dm_schema_tracker.go @@ -0,0 +1,150 @@ +// Copyright 2022 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ddl + +import ( + "github.com/pingcap/tidb/infoschema" + "github.com/pingcap/tidb/parser/model" + "github.com/pingcap/tidb/table" + "github.com/pingcap/tidb/table/tables" +) + +// infoStore is a simple structure that stores DBInfo and TableInfo. It's not thread-safe. +type infoStore struct { + lowerCaseTableNames int // same as variable lower_case_table_names + + dbs map[string]*model.DBInfo + tables map[string]map[string]*model.TableInfo +} + +func newInfoStore(lowerCaseTableNames int) *infoStore { + return &infoStore{ + lowerCaseTableNames: lowerCaseTableNames, + dbs: map[string]*model.DBInfo{}, + tables: map[string]map[string]*model.TableInfo{}, + } +} + +func (i *infoStore) ciStr2Key(name model.CIStr) string { + if i.lowerCaseTableNames == 0 { + return name.O + } + return name.L +} + +// SchemaByName is exported to be used when infoStore is embedded into another public struct. +func (i *infoStore) SchemaByName(name model.CIStr) *model.DBInfo { + key := i.ciStr2Key(name) + return i.dbs[key] +} + +func (i *infoStore) putSchema(dbInfo *model.DBInfo) { + key := i.ciStr2Key(dbInfo.Name) + i.dbs[key] = dbInfo + if i.tables[key] == nil { + i.tables[key] = map[string]*model.TableInfo{} + } +} + +func (i *infoStore) deleteSchema(name model.CIStr) bool { + key := i.ciStr2Key(name) + _, ok := i.dbs[key] + if !ok { + return false + } + delete(i.dbs, key) + delete(i.tables, key) + return true +} + +// TableByName is exported to be used when infoStore is embedded into another public struct. +func (i *infoStore) TableByName(schema, table model.CIStr) (*model.TableInfo, error) { + schemaKey := i.ciStr2Key(schema) + tables, ok := i.tables[schemaKey] + if !ok { + return nil, infoschema.ErrDatabaseNotExists.GenWithStackByArgs(schema) + } + + tableKey := i.ciStr2Key(table) + tbl, ok := tables[tableKey] + if !ok { + return nil, infoschema.ErrTableNotExists.GenWithStackByArgs(schema, table) + } + return tbl, nil +} + +func (i *infoStore) putTable(schemaName model.CIStr, tblInfo *model.TableInfo) error { + schemaKey := i.ciStr2Key(schemaName) + tables, ok := i.tables[schemaKey] + if !ok { + return infoschema.ErrDatabaseNotExists.GenWithStackByArgs(schemaName) + } + tableKey := i.ciStr2Key(tblInfo.Name) + tables[tableKey] = tblInfo + return nil +} + +func (i *infoStore) deleteTable(schema, table model.CIStr) error { + schemaKey := i.ciStr2Key(schema) + tables, ok := i.tables[schemaKey] + if !ok { + return infoschema.ErrDatabaseNotExists.GenWithStackByArgs(schema) + } + + tableKey := i.ciStr2Key(table) + _, ok = tables[tableKey] + if !ok { + return infoschema.ErrTableNotExists.GenWithStackByArgs(schema, table) + } + delete(tables, tableKey) + return nil +} + +// infoSchemaAdaptor convert infoStore to infoschema.InfoSchema, it only implements a part of InfoSchema interface to be +// used by DDL interface. +type infoSchemaAdaptor struct { + infoschema.InfoSchema + inner *infoStore +} + +// SchemaByName implements the InfoSchema interface. +func (i infoSchemaAdaptor) SchemaByName(schema model.CIStr) (*model.DBInfo, bool) { + dbInfo := i.inner.SchemaByName(schema) + return dbInfo, dbInfo != nil +} + +// TableExists implements the InfoSchema interface. +func (i infoSchemaAdaptor) TableExists(schema, table model.CIStr) bool { + tableInfo, _ := i.inner.TableByName(schema, table) + return tableInfo != nil +} + +// TableIsView implements the InfoSchema interface. +func (i infoSchemaAdaptor) TableIsView(schema, table model.CIStr) bool { + tableInfo, _ := i.inner.TableByName(schema, table) + if tableInfo == nil { + return false + } + return tableInfo.IsView() +} + +// TableByName implements the InfoSchema interface. +func (i infoSchemaAdaptor) TableByName(schema, table model.CIStr) (t table.Table, err error) { + tableInfo, err := i.inner.TableByName(schema, table) + if err != nil { + return nil, err + } + return tables.MockTableFromMeta(tableInfo), nil +} diff --git a/ddl/dm_schema_tracker_test.go b/ddl/dm_schema_tracker_test.go new file mode 100644 index 0000000000000..4d7d3e65f0d50 --- /dev/null +++ b/ddl/dm_schema_tracker_test.go @@ -0,0 +1,116 @@ +// Copyright 2022 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ddl + +import ( + "testing" + + "github.com/pingcap/tidb/infoschema" + "github.com/pingcap/tidb/parser/model" + "github.com/stretchr/testify/require" +) + +func TestInfoStoreLowerCaseTableNames(t *testing.T) { + dbName := model.NewCIStr("DBName") + lowerDBName := model.NewCIStr("dbname") + tableName := model.NewCIStr("TableName") + lowerTableName := model.NewCIStr("tablename") + dbInfo := &model.DBInfo{Name: dbName} + tableInfo := &model.TableInfo{Name: tableName} + + // case-sensitive + + is := newInfoStore(0) + is.putSchema(dbInfo) + got := is.SchemaByName(dbName) + require.NotNil(t, got) + got = is.SchemaByName(lowerDBName) + require.Nil(t, got) + + err := is.putTable(lowerDBName, tableInfo) + require.True(t, infoschema.ErrDatabaseNotExists.Equal(err)) + err = is.putTable(dbName, tableInfo) + require.NoError(t, err) + got2, err := is.TableByName(dbName, tableName) + require.NoError(t, err) + require.NotNil(t, got2) + got2, err = is.TableByName(lowerTableName, tableName) + require.True(t, infoschema.ErrDatabaseNotExists.Equal(err)) + require.Nil(t, got2) + got2, err = is.TableByName(dbName, lowerTableName) + require.True(t, infoschema.ErrTableNotExists.Equal(err)) + require.Nil(t, got2) + + // compare-insensitive + + is = newInfoStore(2) + is.putSchema(dbInfo) + got = is.SchemaByName(dbName) + require.NotNil(t, got) + got = is.SchemaByName(lowerDBName) + require.NotNil(t, got) + require.Equal(t, dbName, got.Name) + + err = is.putTable(lowerDBName, tableInfo) + require.NoError(t, err) + got2, err = is.TableByName(dbName, tableName) + require.NoError(t, err) + require.NotNil(t, got2) + got2, err = is.TableByName(dbName, lowerTableName) + require.NoError(t, err) + require.NotNil(t, got2) + require.Equal(t, tableName, got2.Name) +} + +func TestInfoStoreDeleteTables(t *testing.T) { + is := newInfoStore(0) + dbName1 := model.NewCIStr("DBName1") + dbName2 := model.NewCIStr("DBName2") + tableName1 := model.NewCIStr("TableName1") + tableName2 := model.NewCIStr("TableName2") + dbInfo1 := &model.DBInfo{Name: dbName1} + dbInfo2 := &model.DBInfo{Name: dbName2} + tableInfo1 := &model.TableInfo{Name: tableName1} + tableInfo2 := &model.TableInfo{Name: tableName2} + + is.putSchema(dbInfo1) + err := is.putTable(dbName1, tableInfo1) + require.NoError(t, err) + err = is.putTable(dbName1, tableInfo2) + require.NoError(t, err) + + // db2 not created + ok := is.deleteSchema(dbName2) + require.False(t, ok) + err = is.putTable(dbName2, tableInfo1) + require.True(t, infoschema.ErrDatabaseNotExists.Equal(err)) + err = is.deleteTable(dbName2, tableName1) + require.True(t, infoschema.ErrDatabaseNotExists.Equal(err)) + + is.putSchema(dbInfo2) + err = is.putTable(dbName2, tableInfo1) + require.NoError(t, err) + + err = is.deleteTable(dbName2, tableName2) + require.True(t, infoschema.ErrTableNotExists.Equal(err)) + err = is.deleteTable(dbName2, tableName1) + require.NoError(t, err) + + // delete db will remove its tables + ok = is.deleteSchema(dbName1) + require.True(t, ok) + _, err = is.TableByName(dbName1, tableName1) + require.True(t, infoschema.ErrDatabaseNotExists.Equal(err)) +} From 5980b9879cf2b043b79bb3ecf98246062a99f24b Mon Sep 17 00:00:00 2001 From: lance6716 Date: Tue, 5 Jul 2022 16:41:01 +0800 Subject: [PATCH 2/3] fix lint Signed-off-by: lance6716 --- ddl/dm_schema_tracker.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ddl/dm_schema_tracker.go b/ddl/dm_schema_tracker.go index f5bbb3498b62f..aa17e54717477 100644 --- a/ddl/dm_schema_tracker.go +++ b/ddl/dm_schema_tracker.go @@ -114,24 +114,28 @@ func (i *infoStore) deleteTable(schema, table model.CIStr) error { // infoSchemaAdaptor convert infoStore to infoschema.InfoSchema, it only implements a part of InfoSchema interface to be // used by DDL interface. +// nolint:unused type infoSchemaAdaptor struct { infoschema.InfoSchema inner *infoStore } // SchemaByName implements the InfoSchema interface. +// nolint:unused func (i infoSchemaAdaptor) SchemaByName(schema model.CIStr) (*model.DBInfo, bool) { dbInfo := i.inner.SchemaByName(schema) return dbInfo, dbInfo != nil } // TableExists implements the InfoSchema interface. +// nolint:unused func (i infoSchemaAdaptor) TableExists(schema, table model.CIStr) bool { tableInfo, _ := i.inner.TableByName(schema, table) return tableInfo != nil } // TableIsView implements the InfoSchema interface. +// nolint:unused func (i infoSchemaAdaptor) TableIsView(schema, table model.CIStr) bool { tableInfo, _ := i.inner.TableByName(schema, table) if tableInfo == nil { @@ -141,6 +145,7 @@ func (i infoSchemaAdaptor) TableIsView(schema, table model.CIStr) bool { } // TableByName implements the InfoSchema interface. +// nolint:unused func (i infoSchemaAdaptor) TableByName(schema, table model.CIStr) (t table.Table, err error) { tableInfo, err := i.inner.TableByName(schema, table) if err != nil { From 5d55c4b2ec5d88bc4a4c8bd122283be7a4d3862f Mon Sep 17 00:00:00 2001 From: lance6716 Date: Wed, 6 Jul 2022 16:14:38 +0800 Subject: [PATCH 3/3] move and rename --- .../info_store.go | 61 ++++++++++--------- .../info_store_test.go | 55 ++++++++--------- 2 files changed, 60 insertions(+), 56 deletions(-) rename ddl/dm_schema_tracker.go => infoschema/info_store.go (57%) rename ddl/dm_schema_tracker_test.go => infoschema/info_store_test.go (69%) diff --git a/ddl/dm_schema_tracker.go b/infoschema/info_store.go similarity index 57% rename from ddl/dm_schema_tracker.go rename to infoschema/info_store.go index aa17e54717477..9ffd78e64fa2c 100644 --- a/ddl/dm_schema_tracker.go +++ b/infoschema/info_store.go @@ -12,45 +12,46 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ddl +package infoschema import ( - "github.com/pingcap/tidb/infoschema" "github.com/pingcap/tidb/parser/model" "github.com/pingcap/tidb/table" "github.com/pingcap/tidb/table/tables" ) -// infoStore is a simple structure that stores DBInfo and TableInfo. It's not thread-safe. -type infoStore struct { +// InfoStore is a simple structure that stores DBInfo and TableInfo. It's modifiable and not thread-safe. +type InfoStore struct { lowerCaseTableNames int // same as variable lower_case_table_names dbs map[string]*model.DBInfo tables map[string]map[string]*model.TableInfo } -func newInfoStore(lowerCaseTableNames int) *infoStore { - return &infoStore{ +// NewInfoStore creates a InfoStore. +func NewInfoStore(lowerCaseTableNames int) *InfoStore { + return &InfoStore{ lowerCaseTableNames: lowerCaseTableNames, dbs: map[string]*model.DBInfo{}, tables: map[string]map[string]*model.TableInfo{}, } } -func (i *infoStore) ciStr2Key(name model.CIStr) string { +func (i *InfoStore) ciStr2Key(name model.CIStr) string { if i.lowerCaseTableNames == 0 { return name.O } return name.L } -// SchemaByName is exported to be used when infoStore is embedded into another public struct. -func (i *infoStore) SchemaByName(name model.CIStr) *model.DBInfo { +// SchemaByName returns the DBInfo of given name. nil if not found. +func (i *InfoStore) SchemaByName(name model.CIStr) *model.DBInfo { key := i.ciStr2Key(name) return i.dbs[key] } -func (i *infoStore) putSchema(dbInfo *model.DBInfo) { +// PutSchema puts a DBInfo, it will overwrite the old one. +func (i *InfoStore) PutSchema(dbInfo *model.DBInfo) { key := i.ciStr2Key(dbInfo.Name) i.dbs[key] = dbInfo if i.tables[key] == nil { @@ -58,7 +59,8 @@ func (i *infoStore) putSchema(dbInfo *model.DBInfo) { } } -func (i *infoStore) deleteSchema(name model.CIStr) bool { +// DeleteSchema deletes the schema from InfoSchema. Returns true when the schema exists, false otherwise. +func (i *InfoStore) DeleteSchema(name model.CIStr) bool { key := i.ciStr2Key(name) _, ok := i.dbs[key] if !ok { @@ -69,74 +71,77 @@ func (i *infoStore) deleteSchema(name model.CIStr) bool { return true } -// TableByName is exported to be used when infoStore is embedded into another public struct. -func (i *infoStore) TableByName(schema, table model.CIStr) (*model.TableInfo, error) { +// TableByName returns the TableInfo. It will also return the error like an infoschema. +func (i *InfoStore) TableByName(schema, table model.CIStr) (*model.TableInfo, error) { schemaKey := i.ciStr2Key(schema) tables, ok := i.tables[schemaKey] if !ok { - return nil, infoschema.ErrDatabaseNotExists.GenWithStackByArgs(schema) + return nil, ErrDatabaseNotExists.GenWithStackByArgs(schema) } tableKey := i.ciStr2Key(table) tbl, ok := tables[tableKey] if !ok { - return nil, infoschema.ErrTableNotExists.GenWithStackByArgs(schema, table) + return nil, ErrTableNotExists.GenWithStackByArgs(schema, table) } return tbl, nil } -func (i *infoStore) putTable(schemaName model.CIStr, tblInfo *model.TableInfo) error { +// PutTable puts a TableInfo, it will overwrite the old one. If the schema doesn't exist, it will return ErrDatabaseNotExists. +func (i *InfoStore) PutTable(schemaName model.CIStr, tblInfo *model.TableInfo) error { schemaKey := i.ciStr2Key(schemaName) tables, ok := i.tables[schemaKey] if !ok { - return infoschema.ErrDatabaseNotExists.GenWithStackByArgs(schemaName) + return ErrDatabaseNotExists.GenWithStackByArgs(schemaName) } tableKey := i.ciStr2Key(tblInfo.Name) tables[tableKey] = tblInfo return nil } -func (i *infoStore) deleteTable(schema, table model.CIStr) error { +// DeleteTable deletes the TableInfo, it will return ErrDatabaseNotExists or ErrTableNotExists when schema or table does +// not exist. +func (i *InfoStore) DeleteTable(schema, table model.CIStr) error { schemaKey := i.ciStr2Key(schema) tables, ok := i.tables[schemaKey] if !ok { - return infoschema.ErrDatabaseNotExists.GenWithStackByArgs(schema) + return ErrDatabaseNotExists.GenWithStackByArgs(schema) } tableKey := i.ciStr2Key(table) _, ok = tables[tableKey] if !ok { - return infoschema.ErrTableNotExists.GenWithStackByArgs(schema, table) + return ErrTableNotExists.GenWithStackByArgs(schema, table) } delete(tables, tableKey) return nil } -// infoSchemaAdaptor convert infoStore to infoschema.InfoSchema, it only implements a part of InfoSchema interface to be +// InfoStoreAdaptor convert InfoStore to InfoSchema, it only implements a part of InfoSchema interface to be // used by DDL interface. // nolint:unused -type infoSchemaAdaptor struct { - infoschema.InfoSchema - inner *infoStore +type InfoStoreAdaptor struct { + InfoSchema + inner *InfoStore } // SchemaByName implements the InfoSchema interface. // nolint:unused -func (i infoSchemaAdaptor) SchemaByName(schema model.CIStr) (*model.DBInfo, bool) { +func (i InfoStoreAdaptor) SchemaByName(schema model.CIStr) (*model.DBInfo, bool) { dbInfo := i.inner.SchemaByName(schema) return dbInfo, dbInfo != nil } // TableExists implements the InfoSchema interface. // nolint:unused -func (i infoSchemaAdaptor) TableExists(schema, table model.CIStr) bool { +func (i InfoStoreAdaptor) TableExists(schema, table model.CIStr) bool { tableInfo, _ := i.inner.TableByName(schema, table) return tableInfo != nil } // TableIsView implements the InfoSchema interface. // nolint:unused -func (i infoSchemaAdaptor) TableIsView(schema, table model.CIStr) bool { +func (i InfoStoreAdaptor) TableIsView(schema, table model.CIStr) bool { tableInfo, _ := i.inner.TableByName(schema, table) if tableInfo == nil { return false @@ -146,7 +151,7 @@ func (i infoSchemaAdaptor) TableIsView(schema, table model.CIStr) bool { // TableByName implements the InfoSchema interface. // nolint:unused -func (i infoSchemaAdaptor) TableByName(schema, table model.CIStr) (t table.Table, err error) { +func (i InfoStoreAdaptor) TableByName(schema, table model.CIStr) (t table.Table, err error) { tableInfo, err := i.inner.TableByName(schema, table) if err != nil { return nil, err diff --git a/ddl/dm_schema_tracker_test.go b/infoschema/info_store_test.go similarity index 69% rename from ddl/dm_schema_tracker_test.go rename to infoschema/info_store_test.go index 4d7d3e65f0d50..07ee9d87d9de5 100644 --- a/ddl/dm_schema_tracker_test.go +++ b/infoschema/info_store_test.go @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ddl +package infoschema import ( "testing" - "github.com/pingcap/tidb/infoschema" "github.com/pingcap/tidb/parser/model" "github.com/stretchr/testify/require" ) @@ -32,38 +31,38 @@ func TestInfoStoreLowerCaseTableNames(t *testing.T) { // case-sensitive - is := newInfoStore(0) - is.putSchema(dbInfo) + is := NewInfoStore(0) + is.PutSchema(dbInfo) got := is.SchemaByName(dbName) require.NotNil(t, got) got = is.SchemaByName(lowerDBName) require.Nil(t, got) - err := is.putTable(lowerDBName, tableInfo) - require.True(t, infoschema.ErrDatabaseNotExists.Equal(err)) - err = is.putTable(dbName, tableInfo) + err := is.PutTable(lowerDBName, tableInfo) + require.True(t, ErrDatabaseNotExists.Equal(err)) + err = is.PutTable(dbName, tableInfo) require.NoError(t, err) got2, err := is.TableByName(dbName, tableName) require.NoError(t, err) require.NotNil(t, got2) got2, err = is.TableByName(lowerTableName, tableName) - require.True(t, infoschema.ErrDatabaseNotExists.Equal(err)) + require.True(t, ErrDatabaseNotExists.Equal(err)) require.Nil(t, got2) got2, err = is.TableByName(dbName, lowerTableName) - require.True(t, infoschema.ErrTableNotExists.Equal(err)) + require.True(t, ErrTableNotExists.Equal(err)) require.Nil(t, got2) // compare-insensitive - is = newInfoStore(2) - is.putSchema(dbInfo) + is = NewInfoStore(2) + is.PutSchema(dbInfo) got = is.SchemaByName(dbName) require.NotNil(t, got) got = is.SchemaByName(lowerDBName) require.NotNil(t, got) require.Equal(t, dbName, got.Name) - err = is.putTable(lowerDBName, tableInfo) + err = is.PutTable(lowerDBName, tableInfo) require.NoError(t, err) got2, err = is.TableByName(dbName, tableName) require.NoError(t, err) @@ -75,7 +74,7 @@ func TestInfoStoreLowerCaseTableNames(t *testing.T) { } func TestInfoStoreDeleteTables(t *testing.T) { - is := newInfoStore(0) + is := NewInfoStore(0) dbName1 := model.NewCIStr("DBName1") dbName2 := model.NewCIStr("DBName2") tableName1 := model.NewCIStr("TableName1") @@ -85,32 +84,32 @@ func TestInfoStoreDeleteTables(t *testing.T) { tableInfo1 := &model.TableInfo{Name: tableName1} tableInfo2 := &model.TableInfo{Name: tableName2} - is.putSchema(dbInfo1) - err := is.putTable(dbName1, tableInfo1) + is.PutSchema(dbInfo1) + err := is.PutTable(dbName1, tableInfo1) require.NoError(t, err) - err = is.putTable(dbName1, tableInfo2) + err = is.PutTable(dbName1, tableInfo2) require.NoError(t, err) // db2 not created - ok := is.deleteSchema(dbName2) + ok := is.DeleteSchema(dbName2) require.False(t, ok) - err = is.putTable(dbName2, tableInfo1) - require.True(t, infoschema.ErrDatabaseNotExists.Equal(err)) - err = is.deleteTable(dbName2, tableName1) - require.True(t, infoschema.ErrDatabaseNotExists.Equal(err)) + err = is.PutTable(dbName2, tableInfo1) + require.True(t, ErrDatabaseNotExists.Equal(err)) + err = is.DeleteTable(dbName2, tableName1) + require.True(t, ErrDatabaseNotExists.Equal(err)) - is.putSchema(dbInfo2) - err = is.putTable(dbName2, tableInfo1) + is.PutSchema(dbInfo2) + err = is.PutTable(dbName2, tableInfo1) require.NoError(t, err) - err = is.deleteTable(dbName2, tableName2) - require.True(t, infoschema.ErrTableNotExists.Equal(err)) - err = is.deleteTable(dbName2, tableName1) + err = is.DeleteTable(dbName2, tableName2) + require.True(t, ErrTableNotExists.Equal(err)) + err = is.DeleteTable(dbName2, tableName1) require.NoError(t, err) // delete db will remove its tables - ok = is.deleteSchema(dbName1) + ok = is.DeleteSchema(dbName1) require.True(t, ok) _, err = is.TableByName(dbName1, tableName1) - require.True(t, infoschema.ErrDatabaseNotExists.Equal(err)) + require.True(t, ErrDatabaseNotExists.Equal(err)) }