github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/enginetest/dolt_harness.go (about)

     1  // Copyright 2020 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package enginetest
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"runtime"
    21  	"strings"
    22  	"testing"
    23  
    24  	gms "github.com/dolthub/go-mysql-server"
    25  	"github.com/dolthub/go-mysql-server/enginetest"
    26  	"github.com/dolthub/go-mysql-server/enginetest/scriptgen/setup"
    27  	"github.com/dolthub/go-mysql-server/memory"
    28  	"github.com/dolthub/go-mysql-server/sql"
    29  	"github.com/dolthub/go-mysql-server/sql/mysql_db"
    30  	"github.com/dolthub/go-mysql-server/sql/rowexec"
    31  	"github.com/stretchr/testify/require"
    32  
    33  	"github.com/dolthub/dolt/go/libraries/doltcore/branch_control"
    34  	"github.com/dolthub/dolt/go/libraries/doltcore/dtestutils"
    35  	"github.com/dolthub/dolt/go/libraries/doltcore/env"
    36  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle"
    37  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
    38  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/statsnoms"
    39  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/statspro"
    40  	"github.com/dolthub/dolt/go/libraries/utils/filesys"
    41  	"github.com/dolthub/dolt/go/store/types"
    42  )
    43  
    44  type DoltHarness struct {
    45  	t                   *testing.T
    46  	provider            dsess.DoltDatabaseProvider
    47  	statsPro            sql.StatsProvider
    48  	multiRepoEnv        *env.MultiRepoEnv
    49  	session             *dsess.DoltSession
    50  	branchControl       *branch_control.Controller
    51  	parallelism         int
    52  	skippedQueries      []string
    53  	setupData           []setup.SetupScript
    54  	resetData           []setup.SetupScript
    55  	engine              *gms.Engine
    56  	setupDbs            map[string]struct{}
    57  	skipSetupCommit     bool
    58  	configureStats      bool
    59  	useLocalFilesystem  bool
    60  	setupTestProcedures bool
    61  }
    62  
    63  var _ enginetest.Harness = (*DoltHarness)(nil)
    64  var _ enginetest.SkippingHarness = (*DoltHarness)(nil)
    65  var _ enginetest.ClientHarness = (*DoltHarness)(nil)
    66  var _ enginetest.IndexHarness = (*DoltHarness)(nil)
    67  var _ enginetest.VersionedDBHarness = (*DoltHarness)(nil)
    68  var _ enginetest.ForeignKeyHarness = (*DoltHarness)(nil)
    69  var _ enginetest.KeylessTableHarness = (*DoltHarness)(nil)
    70  var _ enginetest.ReadOnlyDatabaseHarness = (*DoltHarness)(nil)
    71  var _ enginetest.ValidatingHarness = (*DoltHarness)(nil)
    72  
    73  // newDoltHarness creates a new harness for testing Dolt, using an in-memory filesystem and an in-memory blob store.
    74  func newDoltHarness(t *testing.T) *DoltHarness {
    75  	dh := &DoltHarness{
    76  		t:              t,
    77  		skippedQueries: defaultSkippedQueries,
    78  		parallelism:    1,
    79  	}
    80  
    81  	return dh
    82  }
    83  
    84  // newDoltHarnessForLocalFilesystem creates a new harness for testing Dolt, using
    85  // the local filesystem for all storage, instead of in-memory versions. This setup
    86  // is useful for testing functionality that requires a real filesystem.
    87  func newDoltHarnessForLocalFilesystem(t *testing.T) *DoltHarness {
    88  	dh := newDoltHarness(t)
    89  	dh.useLocalFilesystem = true
    90  	return dh
    91  }
    92  
    93  var defaultSkippedQueries = []string{
    94  	"show variables",             // we set extra variables
    95  	"show create table fk_tbl",   // we create an extra key for the FK that vanilla gms does not
    96  	"show indexes from",          // we create / expose extra indexes (for foreign keys)
    97  	"show global variables like", // we set extra variables
    98  }
    99  
   100  // Setup sets the setup scripts for this DoltHarness's engine
   101  func (d *DoltHarness) Setup(setupData ...[]setup.SetupScript) {
   102  	d.closeProvider()
   103  	d.engine = nil
   104  	d.provider = nil
   105  	d.setupData = nil
   106  	for i := range setupData {
   107  		d.setupData = append(d.setupData, setupData[i]...)
   108  	}
   109  }
   110  
   111  func (d *DoltHarness) SkipSetupCommit() {
   112  	d.skipSetupCommit = true
   113  }
   114  
   115  // resetScripts returns a set of queries that will reset the given database
   116  // names. If [autoInc], the queries for resetting autoincrement tables are
   117  // included.
   118  func (d *DoltHarness) resetScripts() []setup.SetupScript {
   119  	ctx := enginetest.NewContext(d)
   120  	_, res := enginetest.MustQuery(ctx, d.engine, "select schema_name from information_schema.schemata where schema_name not in ('information_schema');")
   121  	var dbs []string
   122  	for i := range res {
   123  		dbs = append(dbs, res[i][0].(string))
   124  	}
   125  
   126  	var resetCmds []setup.SetupScript
   127  	resetCmds = append(resetCmds, setup.SetupScript{"SET foreign_key_checks=0;"})
   128  	for i := range dbs {
   129  		db := dbs[i]
   130  		resetCmds = append(resetCmds, setup.SetupScript{fmt.Sprintf("use %s", db)})
   131  
   132  		// Any auto increment tables must be dropped and recreated to get a fresh state for the global auto increment
   133  		// sequence trackers
   134  		_, aiTables := enginetest.MustQuery(ctx, d.engine,
   135  			fmt.Sprintf("select distinct table_name from information_schema.columns where extra = 'auto_increment' and table_schema = '%s';", db))
   136  
   137  		for _, tableNameRow := range aiTables {
   138  			tableName := tableNameRow[0].(string)
   139  
   140  			// special handling for auto_increment_tbl, which is expected to start with particular values
   141  			if strings.ToLower(tableName) == "auto_increment_tbl" {
   142  				resetCmds = append(resetCmds, setup.AutoincrementData...)
   143  				continue
   144  			}
   145  
   146  			resetCmds = append(resetCmds, setup.SetupScript{fmt.Sprintf("drop table %s", tableName)})
   147  		}
   148  
   149  		resetCmds = append(resetCmds, setup.SetupScript{"call dolt_clean()"})
   150  		resetCmds = append(resetCmds, setup.SetupScript{"call dolt_reset('--hard', 'head')"})
   151  	}
   152  
   153  	resetCmds = append(resetCmds, setup.SetupScript{"SET foreign_key_checks=1;"})
   154  	for _, db := range dbs {
   155  		if _, ok := d.setupDbs[db]; !ok && db != "mydb" {
   156  			resetCmds = append(resetCmds, setup.SetupScript{fmt.Sprintf("drop database if exists %s", db)})
   157  		}
   158  	}
   159  	resetCmds = append(resetCmds, setup.SetupScript{"use mydb"})
   160  	return resetCmds
   161  }
   162  
   163  // commitScripts returns a set of queries that will commit the working sets of the given database names
   164  func commitScripts(dbs []string) []setup.SetupScript {
   165  	var commitCmds setup.SetupScript
   166  	for i := range dbs {
   167  		db := dbs[i]
   168  		commitCmds = append(commitCmds, fmt.Sprintf("use %s", db))
   169  		commitCmds = append(commitCmds, "call dolt_add('.')")
   170  		commitCmds = append(commitCmds, fmt.Sprintf("call dolt_commit('--allow-empty', '-am', 'checkpoint enginetest database %s', '--date', '1970-01-01T12:00:00')", db))
   171  	}
   172  	commitCmds = append(commitCmds, "use mydb")
   173  	return []setup.SetupScript{commitCmds}
   174  }
   175  
   176  // NewEngine creates a new *gms.Engine or calls reset and clear scripts on the existing
   177  // engine for reuse.
   178  func (d *DoltHarness) NewEngine(t *testing.T) (enginetest.QueryEngine, error) {
   179  	initializeEngine := d.engine == nil
   180  	if initializeEngine {
   181  		d.branchControl = branch_control.CreateDefaultController(context.Background())
   182  
   183  		pro := d.newProvider()
   184  		if d.setupTestProcedures {
   185  			pro = d.newProviderWithProcedures()
   186  		}
   187  		doltProvider, ok := pro.(*sqle.DoltDatabaseProvider)
   188  		require.True(t, ok)
   189  		d.provider = doltProvider
   190  
   191  		statsProv := statspro.NewProvider(d.provider.(*sqle.DoltDatabaseProvider), statsnoms.NewNomsStatsFactory(d.multiRepoEnv.RemoteDialProvider()))
   192  		d.statsPro = statsProv
   193  
   194  		var err error
   195  		d.session, err = dsess.NewDoltSession(enginetest.NewBaseSession(), d.provider, d.multiRepoEnv.Config(), d.branchControl, d.statsPro)
   196  		require.NoError(t, err)
   197  
   198  		e, err := enginetest.NewEngine(t, d, d.provider, d.setupData, d.statsPro)
   199  		if err != nil {
   200  			return nil, err
   201  		}
   202  		e.Analyzer.ExecBuilder = rowexec.DefaultBuilder
   203  		d.engine = e
   204  
   205  		ctx := enginetest.NewContext(d)
   206  		databases := pro.AllDatabases(ctx)
   207  		d.setupDbs = make(map[string]struct{})
   208  		var dbs []string
   209  		for _, db := range databases {
   210  			dbName := db.Name()
   211  			dbs = append(dbs, dbName)
   212  			d.setupDbs[dbName] = struct{}{}
   213  		}
   214  
   215  		if !d.skipSetupCommit {
   216  			e, err = enginetest.RunSetupScripts(ctx, e, commitScripts(dbs), d.SupportsNativeIndexCreation())
   217  			if err != nil {
   218  				return nil, err
   219  			}
   220  		}
   221  
   222  		if d.configureStats {
   223  			bThreads := sql.NewBackgroundThreads()
   224  			e = e.WithBackgroundThreads(bThreads)
   225  
   226  			dSess := dsess.DSessFromSess(ctx.Session)
   227  			dbCache := dSess.DatabaseCache(ctx)
   228  
   229  			dsessDbs := make([]dsess.SqlDatabase, len(dbs))
   230  			for i, dbName := range dbs {
   231  				dsessDbs[i], _ = dbCache.GetCachedRevisionDb(fmt.Sprintf("%s/main", dbName), dbName)
   232  			}
   233  
   234  			ctxFact := func(context.Context) (*sql.Context, error) {
   235  				sess := d.newSessionWithClient(sql.Client{Address: "localhost", User: "root"})
   236  				return sql.NewContext(context.Background(), sql.WithSession(sess)), nil
   237  			}
   238  			if err = statsProv.Configure(ctx, ctxFact, bThreads, dsessDbs); err != nil {
   239  				return nil, err
   240  			}
   241  
   242  			statsOnlyQueries := filterStatsOnlyQueries(d.setupData)
   243  			e, err = enginetest.RunSetupScripts(ctx, e, statsOnlyQueries, d.SupportsNativeIndexCreation())
   244  		}
   245  
   246  		return e, nil
   247  	}
   248  
   249  	// Reset the mysql DB table to a clean state for this new engine
   250  	d.engine.Analyzer.Catalog.MySQLDb = mysql_db.CreateEmptyMySQLDb()
   251  	d.engine.Analyzer.Catalog.MySQLDb.AddRootAccount()
   252  	d.engine.Analyzer.Catalog.StatsProvider = statspro.NewProvider(d.provider.(*sqle.DoltDatabaseProvider), statsnoms.NewNomsStatsFactory(d.multiRepoEnv.RemoteDialProvider()))
   253  
   254  	// Get a fresh session if we are reusing the engine
   255  	if !initializeEngine {
   256  		var err error
   257  		d.session, err = dsess.NewDoltSession(enginetest.NewBaseSession(), d.provider, d.multiRepoEnv.Config(), d.branchControl, d.statsPro)
   258  		require.NoError(t, err)
   259  	}
   260  
   261  	ctx := enginetest.NewContext(d)
   262  	e, err := enginetest.RunSetupScripts(ctx, d.engine, d.resetScripts(), d.SupportsNativeIndexCreation())
   263  
   264  	return e, err
   265  }
   266  
   267  func filterStatsOnlyQueries(scripts []setup.SetupScript) []setup.SetupScript {
   268  	var ret []string
   269  	for i := range scripts {
   270  		for _, s := range scripts[i] {
   271  			if strings.HasPrefix(s, "analyze table") {
   272  				ret = append(ret, s)
   273  			}
   274  		}
   275  	}
   276  	return []setup.SetupScript{ret}
   277  }
   278  
   279  // WithParallelism returns a copy of the harness with parallelism set to the given number of threads. A value of 0 or
   280  // less means to use the system parallelism settings.
   281  func (d *DoltHarness) WithParallelism(parallelism int) *DoltHarness {
   282  	nd := *d
   283  	nd.parallelism = parallelism
   284  	return &nd
   285  }
   286  
   287  // WithSkippedQueries returns a copy of the harness with the given queries skipped
   288  func (d *DoltHarness) WithSkippedQueries(queries []string) *DoltHarness {
   289  	nd := *d
   290  	nd.skippedQueries = append(d.skippedQueries, queries...)
   291  	return &nd
   292  }
   293  
   294  // SkipQueryTest returns whether to skip a query
   295  func (d *DoltHarness) SkipQueryTest(query string) bool {
   296  	lowerQuery := strings.ToLower(query)
   297  	for _, skipped := range d.skippedQueries {
   298  		if strings.Contains(lowerQuery, strings.ToLower(skipped)) {
   299  			return true
   300  		}
   301  	}
   302  
   303  	return false
   304  }
   305  
   306  func (d *DoltHarness) Parallelism() int {
   307  	if d.parallelism <= 0 {
   308  
   309  		// always test with some parallelism
   310  		parallelism := runtime.NumCPU()
   311  
   312  		if parallelism <= 1 {
   313  			parallelism = 2
   314  		}
   315  
   316  		return parallelism
   317  	}
   318  
   319  	return d.parallelism
   320  }
   321  
   322  func (d *DoltHarness) NewContext() *sql.Context {
   323  	return sql.NewContext(context.Background(), sql.WithSession(d.session))
   324  }
   325  
   326  func (d *DoltHarness) NewContextWithClient(client sql.Client) *sql.Context {
   327  	return sql.NewContext(context.Background(), sql.WithSession(d.newSessionWithClient(client)))
   328  }
   329  
   330  func (d *DoltHarness) NewSession() *sql.Context {
   331  	d.session = d.newSessionWithClient(sql.Client{Address: "localhost", User: "root"})
   332  	return d.NewContext()
   333  }
   334  
   335  func (d *DoltHarness) newSessionWithClient(client sql.Client) *dsess.DoltSession {
   336  	localConfig := d.multiRepoEnv.Config()
   337  	pro := d.session.Provider()
   338  
   339  	dSession, err := dsess.NewDoltSession(sql.NewBaseSessionWithClientServer("address", client, 1), pro.(dsess.DoltDatabaseProvider), localConfig, d.branchControl, d.statsPro)
   340  	dSession.SetCurrentDatabase("mydb")
   341  	require.NoError(d.t, err)
   342  	return dSession
   343  }
   344  
   345  func (d *DoltHarness) SupportsNativeIndexCreation() bool {
   346  	return true
   347  }
   348  
   349  func (d *DoltHarness) SupportsForeignKeys() bool {
   350  	return true
   351  }
   352  
   353  func (d *DoltHarness) SupportsKeylessTables() bool {
   354  	return true
   355  }
   356  
   357  func (d *DoltHarness) NewDatabases(names ...string) []sql.Database {
   358  	d.closeProvider()
   359  	d.engine = nil
   360  	d.provider = nil
   361  
   362  	d.branchControl = branch_control.CreateDefaultController(context.Background())
   363  
   364  	pro := d.newProvider()
   365  	doltProvider, ok := pro.(*sqle.DoltDatabaseProvider)
   366  	require.True(d.t, ok)
   367  	d.provider = doltProvider
   368  	d.statsPro = statspro.NewProvider(doltProvider, statsnoms.NewNomsStatsFactory(d.multiRepoEnv.RemoteDialProvider()))
   369  
   370  	var err error
   371  	d.session, err = dsess.NewDoltSession(enginetest.NewBaseSession(), doltProvider, d.multiRepoEnv.Config(), d.branchControl, d.statsPro)
   372  	require.NoError(d.t, err)
   373  
   374  	// TODO: the engine tests should do this for us
   375  	d.session.SetCurrentDatabase("mydb")
   376  
   377  	e := enginetest.NewEngineWithProvider(d.t, d, d.provider)
   378  	require.NoError(d.t, err)
   379  	d.engine = e
   380  
   381  	for _, name := range names {
   382  		err := d.provider.CreateDatabase(enginetest.NewContext(d), name)
   383  		require.NoError(d.t, err)
   384  	}
   385  
   386  	ctx := enginetest.NewContext(d)
   387  	databases := pro.AllDatabases(ctx)
   388  
   389  	// It's important that we return the databases in the same order as the names argument
   390  	var dbs []sql.Database
   391  	for _, name := range names {
   392  		for _, db := range databases {
   393  			if db.Name() == name {
   394  				dbs = append(dbs, db)
   395  				break
   396  			}
   397  		}
   398  	}
   399  
   400  	return dbs
   401  }
   402  
   403  func (d *DoltHarness) NewReadOnlyEngine(provider sql.DatabaseProvider) (enginetest.QueryEngine, error) {
   404  	ddp, ok := provider.(*sqle.DoltDatabaseProvider)
   405  	if !ok {
   406  		return nil, fmt.Errorf("expected a DoltDatabaseProvider")
   407  	}
   408  
   409  	allDatabases := ddp.AllDatabases(d.NewContext())
   410  	dbs := make([]dsess.SqlDatabase, len(allDatabases))
   411  	locations := make([]filesys.Filesys, len(allDatabases))
   412  
   413  	for i, db := range allDatabases {
   414  		dbs[i] = sqle.ReadOnlyDatabase{Database: db.(sqle.Database)}
   415  		loc, err := ddp.FileSystemForDatabase(db.Name())
   416  		if err != nil {
   417  			return nil, err
   418  		}
   419  
   420  		locations[i] = loc
   421  	}
   422  
   423  	readOnlyProvider, err := sqle.NewDoltDatabaseProviderWithDatabases("main", ddp.FileSystem(), dbs, locations)
   424  	if err != nil {
   425  		return nil, err
   426  	}
   427  
   428  	// reset the session as well since we have swapped out the database provider, which invalidates caching assumptions
   429  	d.session, err = dsess.NewDoltSession(enginetest.NewBaseSession(), readOnlyProvider, d.multiRepoEnv.Config(), d.branchControl, d.statsPro)
   430  	require.NoError(d.t, err)
   431  
   432  	return enginetest.NewEngineWithProvider(nil, d, readOnlyProvider), nil
   433  }
   434  
   435  func (d *DoltHarness) NewDatabaseProvider() sql.MutableDatabaseProvider {
   436  	return d.provider
   437  }
   438  
   439  func (d *DoltHarness) Close() {
   440  	d.closeProvider()
   441  }
   442  
   443  func (d *DoltHarness) closeProvider() {
   444  	if d.provider != nil {
   445  		dbs := d.provider.AllDatabases(sql.NewEmptyContext())
   446  		for _, db := range dbs {
   447  			require.NoError(d.t, db.(dsess.SqlDatabase).DbData().Ddb.Close())
   448  		}
   449  	}
   450  }
   451  
   452  func (d *DoltHarness) newProvider() sql.MutableDatabaseProvider {
   453  	d.closeProvider()
   454  
   455  	var dEnv *env.DoltEnv
   456  	if d.useLocalFilesystem {
   457  		dEnv = dtestutils.CreateTestEnvForLocalFilesystem()
   458  	} else {
   459  		dEnv = dtestutils.CreateTestEnv()
   460  	}
   461  	defer dEnv.DoltDB.Close()
   462  
   463  	store := dEnv.DoltDB.ValueReadWriter().(*types.ValueStore)
   464  	store.SetValidateContentAddresses(true)
   465  
   466  	mrEnv, err := env.MultiEnvForDirectory(context.Background(), dEnv.Config.WriteableConfig(), dEnv.FS, dEnv.Version, dEnv)
   467  	require.NoError(d.t, err)
   468  	d.multiRepoEnv = mrEnv
   469  
   470  	b := env.GetDefaultInitBranch(d.multiRepoEnv.Config())
   471  	pro, err := sqle.NewDoltDatabaseProvider(b, d.multiRepoEnv.FileSystem())
   472  	require.NoError(d.t, err)
   473  
   474  	return pro
   475  }
   476  
   477  func (d *DoltHarness) newProviderWithProcedures() sql.MutableDatabaseProvider {
   478  	pro := d.newProvider()
   479  	provider, ok := pro.(*sqle.DoltDatabaseProvider)
   480  	require.True(d.t, ok)
   481  	for _, esp := range memory.ExternalStoredProcedures {
   482  		provider.Register(esp)
   483  	}
   484  	return provider
   485  }
   486  
   487  func (d *DoltHarness) newTable(db sql.Database, name string, schema sql.PrimaryKeySchema) (sql.Table, error) {
   488  	tc := db.(sql.TableCreator)
   489  
   490  	ctx := enginetest.NewContext(d)
   491  	ctx.Session.SetCurrentDatabase(db.Name())
   492  	err := tc.CreateTable(ctx, name, schema, sql.Collation_Default, "")
   493  	if err != nil {
   494  		return nil, err
   495  	}
   496  
   497  	ctx = enginetest.NewContext(d)
   498  	ctx.Session.SetCurrentDatabase(db.Name())
   499  	table, ok, err := db.GetTableInsensitive(ctx, name)
   500  	require.NoError(d.t, err)
   501  	require.True(d.t, ok, "table %s not found after creation", name)
   502  	return table, nil
   503  }
   504  
   505  // NewTableAsOf implements enginetest.VersionedHarness
   506  // Dolt doesn't version tables per se, just the entire database. So ignore the name and schema and just create a new
   507  // branch with the given name.
   508  func (d *DoltHarness) NewTableAsOf(db sql.VersionedDatabase, name string, schema sql.PrimaryKeySchema, asOf interface{}) sql.Table {
   509  	table, err := d.newTable(db, name, schema)
   510  	if err != nil {
   511  		require.True(d.t, sql.ErrTableAlreadyExists.Is(err))
   512  	}
   513  
   514  	table, ok, err := db.GetTableInsensitive(enginetest.NewContext(d), name)
   515  	require.NoError(d.t, err)
   516  	require.True(d.t, ok)
   517  
   518  	return table
   519  }
   520  
   521  // SnapshotTable implements enginetest.VersionedHarness
   522  // Dolt doesn't version tables per se, just the entire database. So ignore the name and schema and just create a new
   523  // branch with the given name.
   524  func (d *DoltHarness) SnapshotTable(db sql.VersionedDatabase, tableName string, asOf interface{}) error {
   525  	e := enginetest.NewEngineWithProvider(d.t, d, d.NewDatabaseProvider())
   526  
   527  	asOfString, ok := asOf.(string)
   528  	require.True(d.t, ok)
   529  
   530  	ctx := enginetest.NewContext(d)
   531  
   532  	_, iter, err := e.Query(ctx,
   533  		"CALL DOLT_COMMIT('-Am', 'test commit');")
   534  	require.NoError(d.t, err)
   535  	_, err = sql.RowIterToRows(ctx, iter)
   536  	require.NoError(d.t, err)
   537  
   538  	// Create a new branch at this commit with the given identifier
   539  	ctx = enginetest.NewContext(d)
   540  	query := "CALL dolt_branch('" + asOfString + "')"
   541  
   542  	_, iter, err = e.Query(ctx,
   543  		query)
   544  	require.NoError(d.t, err)
   545  	_, err = sql.RowIterToRows(ctx, iter)
   546  	require.NoError(d.t, err)
   547  
   548  	return nil
   549  }
   550  
   551  func (d *DoltHarness) ValidateEngine(ctx *sql.Context, e *gms.Engine) (err error) {
   552  	for _, db := range e.Analyzer.Catalog.AllDatabases(ctx) {
   553  		if err = ValidateDatabase(ctx, db); err != nil {
   554  			return err
   555  		}
   556  	}
   557  	return
   558  }