github.com/duskeagle/pop@v4.10.1-0.20190417200916-92f2b794aab5+incompatible/dialect_sqlite.go (about)

     1  // +build sqlite
     2  
     3  package pop
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/gobuffalo/fizz"
    17  	"github.com/gobuffalo/fizz/translators"
    18  	"github.com/gobuffalo/pop/columns"
    19  	"github.com/gobuffalo/pop/logging"
    20  	"github.com/markbates/going/defaults"
    21  	_ "github.com/mattn/go-sqlite3" // Load SQLite3 CGo driver
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  const nameSQLite3 = "sqlite3"
    26  
    27  func init() {
    28  	AvailableDialects = append(AvailableDialects, nameSQLite3)
    29  	dialectSynonyms["sqlite"] = nameSQLite3
    30  	urlParser[nameSQLite3] = urlParserSQLite3
    31  	newConnection[nameSQLite3] = newSQLite
    32  }
    33  
    34  var _ dialect = &sqlite{}
    35  
    36  type sqlite struct {
    37  	commonDialect
    38  	gil   *sync.Mutex
    39  	smGil *sync.Mutex
    40  }
    41  
    42  func (m *sqlite) Name() string {
    43  	return nameSQLite3
    44  }
    45  
    46  func (m *sqlite) Details() *ConnectionDetails {
    47  	return m.ConnectionDetails
    48  }
    49  
    50  func (m *sqlite) URL() string {
    51  	return m.ConnectionDetails.Database + "?_busy_timeout=5000"
    52  }
    53  
    54  func (m *sqlite) MigrationURL() string {
    55  	return m.ConnectionDetails.URL
    56  }
    57  
    58  func (m *sqlite) Create(s store, model *Model, cols columns.Columns) error {
    59  	return m.locker(m.smGil, func() error {
    60  		keyType := model.PrimaryKeyType()
    61  		switch keyType {
    62  		case "int", "int64":
    63  			var id int64
    64  			w := cols.Writeable()
    65  			var query string
    66  			if len(w.Cols) > 0 {
    67  				query = fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", model.TableName(), w.String(), w.SymbolizedString())
    68  			} else {
    69  				query = fmt.Sprintf("INSERT INTO %s DEFAULT VALUES", model.TableName())
    70  			}
    71  			log(logging.SQL, query)
    72  			res, err := s.NamedExec(query, model.Value)
    73  			if err != nil {
    74  				return errors.WithStack(err)
    75  			}
    76  			id, err = res.LastInsertId()
    77  			if err == nil {
    78  				model.setID(id)
    79  			}
    80  			if err != nil {
    81  				return errors.WithStack(err)
    82  			}
    83  			return nil
    84  		}
    85  		return errors.Wrap(genericCreate(s, model, cols), "sqlite create")
    86  	})
    87  }
    88  
    89  func (m *sqlite) Update(s store, model *Model, cols columns.Columns) error {
    90  	return m.locker(m.smGil, func() error {
    91  		return errors.Wrap(genericUpdate(s, model, cols), "sqlite update")
    92  	})
    93  }
    94  
    95  func (m *sqlite) Destroy(s store, model *Model) error {
    96  	return m.locker(m.smGil, func() error {
    97  		return errors.Wrap(genericDestroy(s, model), "sqlite destroy")
    98  	})
    99  }
   100  
   101  func (m *sqlite) SelectOne(s store, model *Model, query Query) error {
   102  	return m.locker(m.smGil, func() error {
   103  		return errors.Wrap(genericSelectOne(s, model, query), "sqlite select one")
   104  	})
   105  }
   106  
   107  func (m *sqlite) SelectMany(s store, models *Model, query Query) error {
   108  	return m.locker(m.smGil, func() error {
   109  		return errors.Wrap(genericSelectMany(s, models, query), "sqlite select many")
   110  	})
   111  }
   112  
   113  func (m *sqlite) Lock(fn func() error) error {
   114  	return m.locker(m.gil, fn)
   115  }
   116  
   117  func (m *sqlite) locker(l *sync.Mutex, fn func() error) error {
   118  	if defaults.String(m.Details().Options["lock"], "true") == "true" {
   119  		defer l.Unlock()
   120  		l.Lock()
   121  	}
   122  	err := fn()
   123  	attempts := 0
   124  	for err != nil && err.Error() == "database is locked" && attempts <= m.Details().RetryLimit() {
   125  		time.Sleep(m.Details().RetrySleep())
   126  		err = fn()
   127  		attempts++
   128  	}
   129  	return err
   130  }
   131  
   132  func (m *sqlite) CreateDB() error {
   133  	d := filepath.Dir(m.ConnectionDetails.Database)
   134  	err := os.MkdirAll(d, 0766)
   135  	if err != nil {
   136  		return errors.Wrapf(err, "could not create SQLite database %s", m.ConnectionDetails.Database)
   137  	}
   138  	_, err = os.Create(m.ConnectionDetails.Database)
   139  	if err != nil {
   140  		return errors.Wrapf(err, "could not create SQLite database %s", m.ConnectionDetails.Database)
   141  	}
   142  
   143  	log(logging.Info, "created database %s", m.ConnectionDetails.Database)
   144  	return nil
   145  }
   146  
   147  func (m *sqlite) DropDB() error {
   148  	err := os.Remove(m.ConnectionDetails.Database)
   149  	if err != nil {
   150  		return errors.Wrapf(err, "could not drop SQLite database %s", m.ConnectionDetails.Database)
   151  	}
   152  	log(logging.Info, "dropped database %s", m.ConnectionDetails.Database)
   153  	return nil
   154  }
   155  
   156  func (m *sqlite) TranslateSQL(sql string) string {
   157  	return sql
   158  }
   159  
   160  func (m *sqlite) FizzTranslator() fizz.Translator {
   161  	return translators.NewSQLite(m.Details().Database)
   162  }
   163  
   164  func (m *sqlite) DumpSchema(w io.Writer) error {
   165  	cmd := exec.Command("sqlite3", m.Details().Database, ".schema")
   166  	return genericDumpSchema(m.Details(), cmd, w)
   167  }
   168  
   169  func (m *sqlite) LoadSchema(r io.Reader) error {
   170  	cmd := exec.Command("sqlite3", m.ConnectionDetails.Database)
   171  	in, err := cmd.StdinPipe()
   172  	if err != nil {
   173  		return err
   174  	}
   175  	go func() {
   176  		defer in.Close()
   177  		io.Copy(in, r)
   178  	}()
   179  	log(logging.SQL, strings.Join(cmd.Args, " "))
   180  	err = cmd.Start()
   181  	if err != nil {
   182  		return err
   183  	}
   184  
   185  	err = cmd.Wait()
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	log(logging.Info, "loaded schema for %s", m.Details().Database)
   191  	return nil
   192  }
   193  
   194  func (m *sqlite) TruncateAll(tx *Connection) error {
   195  	const tableNames = `SELECT name FROM sqlite_master WHERE type = "table"`
   196  	names := []struct {
   197  		Name string `db:"name"`
   198  	}{}
   199  
   200  	err := tx.RawQuery(tableNames).All(context.TODO(), &names)
   201  	if err != nil {
   202  		return err
   203  	}
   204  	if len(names) == 0 {
   205  		return nil
   206  	}
   207  	stmts := []string{}
   208  	for _, n := range names {
   209  		stmts = append(stmts, fmt.Sprintf("DELETE FROM %s", n.Name))
   210  	}
   211  	return tx.RawQuery(strings.Join(stmts, "; ")).Exec()
   212  }
   213  
   214  func newSQLite(deets *ConnectionDetails) (dialect, error) {
   215  	deets.URL = fmt.Sprintf("sqlite3://%s", deets.Database)
   216  	cd := &sqlite{
   217  		gil:           &sync.Mutex{},
   218  		smGil:         &sync.Mutex{},
   219  		commonDialect: commonDialect{ConnectionDetails: deets},
   220  	}
   221  
   222  	return cd, nil
   223  }
   224  
   225  func urlParserSQLite3(cd *ConnectionDetails) error {
   226  	db := strings.TrimPrefix(cd.URL, "sqlite://")
   227  	cd.Database = strings.TrimPrefix(db, "sqlite3://")
   228  	return nil
   229  }