vitess.io/vitess@v0.16.2/go/vt/vtorc/db/db.go (about)

     1  /*
     2     Copyright 2014 Outbrain Inc.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package db
    18  
    19  import (
    20  	"database/sql"
    21  	"strings"
    22  
    23  	"vitess.io/vitess/go/vt/external/golib/sqlutils"
    24  	"vitess.io/vitess/go/vt/log"
    25  	"vitess.io/vitess/go/vt/vtorc/config"
    26  )
    27  
    28  var (
    29  	Db DB = (*vtorcDB)(nil)
    30  )
    31  
    32  type DB interface {
    33  	QueryVTOrc(query string, argsArray []any, onRow func(sqlutils.RowMap) error) error
    34  }
    35  
    36  type vtorcDB struct {
    37  }
    38  
    39  var _ DB = (*vtorcDB)(nil)
    40  
    41  func (m *vtorcDB) QueryVTOrc(query string, argsArray []any, onRow func(sqlutils.RowMap) error) error {
    42  	return QueryVTOrc(query, argsArray, onRow)
    43  }
    44  
    45  type DummySQLResult struct {
    46  }
    47  
    48  func (dummyRes DummySQLResult) LastInsertId() (int64, error) {
    49  	return 0, nil
    50  }
    51  
    52  func (dummyRes DummySQLResult) RowsAffected() (int64, error) {
    53  	return 1, nil
    54  }
    55  
    56  // OpenTopology returns the DB instance for the vtorc backed database
    57  func OpenVTOrc() (db *sql.DB, err error) {
    58  	var fromCache bool
    59  	db, fromCache, err = sqlutils.GetSQLiteDB(config.Config.SQLite3DataFile)
    60  	if err == nil && !fromCache {
    61  		log.Infof("Connected to vtorc backend: sqlite on %v", config.Config.SQLite3DataFile)
    62  		_ = initVTOrcDB(db)
    63  	}
    64  	if db != nil {
    65  		db.SetMaxOpenConns(1)
    66  		db.SetMaxIdleConns(1)
    67  	}
    68  	return db, err
    69  }
    70  
    71  func translateStatement(statement string) string {
    72  	return sqlutils.ToSqlite3Dialect(statement)
    73  }
    74  
    75  // registerVTOrcDeployment updates the vtorc_metadata table upon successful deployment
    76  func registerVTOrcDeployment(db *sql.DB) error {
    77  	query := `
    78      	replace into vtorc_db_deployments (
    79  				deployed_version, deployed_timestamp
    80  			) values (
    81  				?, NOW()
    82  			)
    83  				`
    84  	if _, err := execInternal(db, query, ""); err != nil {
    85  		log.Fatalf("Unable to write to vtorc_metadata: %+v", err)
    86  	}
    87  	return nil
    88  }
    89  
    90  // deployStatements will issue given sql queries that are not already known to be deployed.
    91  // This iterates both lists (to-run and already-deployed) and also verifies no contraditions.
    92  func deployStatements(db *sql.DB, queries []string) error {
    93  	tx, err := db.Begin()
    94  	if err != nil {
    95  		log.Fatal(err.Error())
    96  	}
    97  	for _, query := range queries {
    98  		query = translateStatement(query)
    99  		if _, err := tx.Exec(query); err != nil {
   100  			if strings.Contains(err.Error(), "syntax error") {
   101  				log.Fatalf("Cannot initiate vtorc: %+v; query=%+v", err, query)
   102  				return err
   103  			}
   104  			if !sqlutils.IsAlterTable(query) && !sqlutils.IsCreateIndex(query) && !sqlutils.IsDropIndex(query) {
   105  				log.Fatalf("Cannot initiate vtorc: %+v; query=%+v", err, query)
   106  				return err
   107  			}
   108  			if !strings.Contains(err.Error(), "duplicate column name") &&
   109  				!strings.Contains(err.Error(), "Duplicate column name") &&
   110  				!strings.Contains(err.Error(), "check that column/key exists") &&
   111  				!strings.Contains(err.Error(), "already exists") &&
   112  				!strings.Contains(err.Error(), "Duplicate key name") {
   113  				log.Errorf("Error initiating vtorc: %+v; query=%+v", err, query)
   114  			}
   115  		}
   116  	}
   117  	if err := tx.Commit(); err != nil {
   118  		log.Fatal(err.Error())
   119  	}
   120  	return nil
   121  }
   122  
   123  // ClearVTOrcDatabase is used to clear the VTOrc database. This function is meant to be used by tests to clear the
   124  // database to get a clean slate without starting a new one.
   125  func ClearVTOrcDatabase() {
   126  	db, _, _ := sqlutils.GetSQLiteDB(config.Config.SQLite3DataFile)
   127  	if db != nil {
   128  		_ = initVTOrcDB(db)
   129  	}
   130  }
   131  
   132  // initVTOrcDB attempts to create/upgrade the vtorc backend database. It is created once in the
   133  // application's lifetime.
   134  func initVTOrcDB(db *sql.DB) error {
   135  	log.Info("Initializing vtorc")
   136  	log.Info("Migrating database schema")
   137  	_ = deployStatements(db, vtorcBackend)
   138  	_ = registerVTOrcDeployment(db)
   139  
   140  	_, _ = ExecVTOrc(`PRAGMA journal_mode = WAL`)
   141  	_, _ = ExecVTOrc(`PRAGMA synchronous = NORMAL`)
   142  
   143  	return nil
   144  }
   145  
   146  // execInternal
   147  func execInternal(db *sql.DB, query string, args ...any) (sql.Result, error) {
   148  	var err error
   149  	query = translateStatement(query)
   150  	res, err := sqlutils.ExecNoPrepare(db, query, args...)
   151  	return res, err
   152  }
   153  
   154  // ExecVTOrc will execute given query on the vtorc backend database.
   155  func ExecVTOrc(query string, args ...any) (sql.Result, error) {
   156  	var err error
   157  	query = translateStatement(query)
   158  	db, err := OpenVTOrc()
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  	res, err := sqlutils.ExecNoPrepare(db, query, args...)
   163  	return res, err
   164  }
   165  
   166  // QueryVTOrcRowsMap
   167  func QueryVTOrcRowsMap(query string, onRow func(sqlutils.RowMap) error) error {
   168  	query = translateStatement(query)
   169  	db, err := OpenVTOrc()
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	return sqlutils.QueryRowsMap(db, query, onRow)
   175  }
   176  
   177  // QueryVTOrc
   178  func QueryVTOrc(query string, argsArray []any, onRow func(sqlutils.RowMap) error) error {
   179  	query = translateStatement(query)
   180  	db, err := OpenVTOrc()
   181  	if err != nil {
   182  		return err
   183  	}
   184  
   185  	if err = sqlutils.QueryRowsMap(db, query, onRow, argsArray...); err != nil {
   186  		log.Warning(err.Error())
   187  	}
   188  
   189  	return err
   190  }
   191  
   192  // ReadTimeNow reads and returns the current timestamp as string. This is an unfortunate workaround
   193  // to support both MySQL and SQLite in all possible timezones. SQLite only speaks UTC where MySQL has
   194  // timezone support. By reading the time as string we get the database's de-facto notion of the time,
   195  // which we can then feed back to it.
   196  func ReadTimeNow() (timeNow string, err error) {
   197  	err = QueryVTOrc(`select now() as time_now`, nil, func(m sqlutils.RowMap) error {
   198  		timeNow = m.GetString("time_now")
   199  		return nil
   200  	})
   201  	return timeNow, err
   202  }