github.com/solongordon/pop@v4.10.0+incompatible/dialect_sqlite.go (about)

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