132 lines
3.0 KiB
Go
132 lines
3.0 KiB
Go
package sqlite3
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/Masterminds/squirrel"
|
|
"github.com/cortezaproject/corteza-server/pkg/ql"
|
|
"github.com/cortezaproject/corteza-server/store"
|
|
"github.com/cortezaproject/corteza-server/store/rdbms"
|
|
"github.com/cortezaproject/corteza-server/store/rdbms/instrumentation"
|
|
"github.com/mattn/go-sqlite3"
|
|
"github.com/ngrok/sqlmw"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type (
|
|
Store struct {
|
|
*rdbms.Store
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
store.Register(Connect, "sqlite3", "sqlite3+debug")
|
|
sql.Register("sqlite3+debug", sqlmw.Driver(new(sqlite3.SQLiteDriver), instrumentation.Debug()))
|
|
}
|
|
|
|
func Connect(ctx context.Context, dsn string) (store.Storer, error) {
|
|
var (
|
|
err error
|
|
cfg *rdbms.Config
|
|
|
|
s = new(Store)
|
|
)
|
|
if cfg, err = ProcDataSourceName(dsn); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cfg.PlaceholderFormat = squirrel.Dollar
|
|
cfg.TxRetryErrHandler = txRetryErrHandler
|
|
cfg.ErrorHandler = errorHandler
|
|
|
|
// Using transactions in SQLite causes table locking
|
|
// @todo there must be a better way to go around this
|
|
cfg.TxDisabled = true
|
|
cfg.SqlFunctionHandler = sqlFunctionHandler
|
|
cfg.ASTFormatter = sqlASTFormatter
|
|
cfg.CastModuleFieldToColumnType = fieldToColumnTypeCaster
|
|
|
|
if s.Store, err = rdbms.Connect(ctx, cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ql.QueryEncoder = &QueryEncoder{}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func ConnectInMemory(ctx context.Context) (s store.Storer, err error) {
|
|
return Connect(ctx, "sqlite3://file::memory:?cache=shared&mode=memory")
|
|
}
|
|
|
|
func ConnectInMemoryWithDebug(ctx context.Context) (s store.Storer, err error) {
|
|
return Connect(ctx, "sqlite3+debug://file::memory:?cache=shared&mode=memory")
|
|
}
|
|
|
|
func (s *Store) Upgrade(ctx context.Context, log *zap.Logger) (err error) {
|
|
if err = (&rdbms.Schema{}).Upgrade(ctx, NewUpgrader(log, s)); err != nil {
|
|
return fmt.Errorf("cannot upgrade sqlite schema: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ProcDataSourceName validates given DSN and ensures
|
|
// params are present and correct
|
|
func ProcDataSourceName(in string) (*rdbms.Config, error) {
|
|
const (
|
|
schemeDel = "://"
|
|
validScheme = "sqlite3"
|
|
)
|
|
|
|
var (
|
|
endOfSchema = strings.Index(in, schemeDel)
|
|
c = &rdbms.Config{}
|
|
)
|
|
|
|
if endOfSchema > 0 && (in[:endOfSchema] == validScheme || strings.HasPrefix(in[:endOfSchema], validScheme+"+")) {
|
|
c.DriverName = in[:endOfSchema]
|
|
c.DataSourceName = in[endOfSchema+len(schemeDel):]
|
|
} else {
|
|
return nil, fmt.Errorf("expecting valid schema (sqlite3://) at the beginning of the DSN (%s)", in)
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func txRetryErrHandler(try int, err error) bool {
|
|
for errors.Unwrap(err) != nil {
|
|
err = errors.Unwrap(err)
|
|
}
|
|
|
|
var sqliteErr, ok = err.(sqlite3.Error)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
switch sqliteErr.Code {
|
|
case sqlite3.ErrLocked:
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func errorHandler(err error) error {
|
|
if err != nil {
|
|
if implErr, ok := err.(sqlite3.Error); ok {
|
|
switch implErr.ExtendedCode {
|
|
case sqlite3.ErrConstraintUnique:
|
|
return store.ErrNotUnique.Wrap(implErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|