Add database helper
This commit is contained in:
parent
7aae1d3e1b
commit
968104c469
10
go.mod
10
go.mod
|
@ -1,14 +1,24 @@
|
|||
module github.com/sorintlab/agola
|
||||
|
||||
require (
|
||||
github.com/Masterminds/squirrel v1.1.0
|
||||
github.com/go-ini/ini v1.42.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.4.1 // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/lib/pq v1.0.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.10.0
|
||||
github.com/minio/minio-go v6.0.14+incompatible
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/pkg/errors v0.8.0
|
||||
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
||||
go.etcd.io/etcd v0.0.0-20181128220305-dedae6eb7c25
|
||||
go.uber.org/zap v1.9.1
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 // indirect
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e // indirect
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
|
||||
google.golang.org/appengine v1.4.0 // indirect
|
||||
gopkg.in/ini.v1 v1.42.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
)
|
||||
|
|
25
go.sum
25
go.sum
|
@ -1,3 +1,5 @@
|
|||
github.com/Masterminds/squirrel v1.1.0 h1:baP1qLdoQCeTw3ifCdOq2dkYc6vGcmRdaociKLbEJXs=
|
||||
github.com/Masterminds/squirrel v1.1.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
||||
|
@ -22,6 +24,8 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
|||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-ini/ini v1.42.0 h1:TWr1wGj35+UiWHlBA8er89seFXxzwFn11spilrrj+38=
|
||||
github.com/go-ini/ini v1.42.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/gogo/protobuf v1.0.0 h1:2jyBKDKU/8v3v2xVR2PtiWQviFUyiaGk2rpfyFT8rTM=
|
||||
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
|
@ -34,6 +38,8 @@ github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a h1:ZJu5NB1Bk5ms4vw0Xu
|
|||
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c h1:Lh2aW+HnU2Nbe1gqD9SOJLJxW1jBMmQOktN2acDyJk8=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
|
||||
|
@ -48,13 +54,23 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
|
|||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kr/pty v1.0.0 h1:jR04h3bskdxb8xt+5B6MoxPwDhMCe0oEgxug4Ca1YSA=
|
||||
github.com/kr/pty v1.0.0/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.0 h1:YNOwxxSJzSUARoD9KRZLzM9Y858MNGCOACTvCW9TSAc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/minio/minio-go v6.0.14+incompatible h1:fnV+GD28LeqdN6vT2XdGKW8Qe/IfjJDswNVuni6km9o=
|
||||
|
@ -80,6 +96,10 @@ github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be h1:MoyXp/VjXUwM0
|
|||
github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/sirupsen/logrus v1.0.5 h1:8c8b5uO0zS4X6RPl/sd1ENwSkIc0/H2PaHxE3udaE8I=
|
||||
github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac h1:wbW+Bybf9pXxnCFAOWZTqkRjAc7rAIwo2e1ArUhiHxg=
|
||||
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
|
@ -108,6 +128,7 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|||
golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg=
|
||||
|
@ -122,6 +143,8 @@ golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
|||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180608181217-32ee49c4dd80 h1:GL7nK1hkDKrkor0eVOYcMdIsUGErFnaC2gpBOVC+vbI=
|
||||
google.golang.org/genproto v0.0.0-20180608181217-32ee49c4dd80/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.14.0 h1:ArxJuB1NWfPY6r9Gp9gqwplT0Ge7nqv9msgu03lHLmo=
|
||||
|
@ -135,6 +158,8 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
|||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2019 Sorint.lab
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const dbVersionTableDDLTmpl = `
|
||||
create table if not exists dbversion (version int not null, time timestamptz not null)
|
||||
`
|
||||
|
||||
const dbVersion = 1
|
||||
|
||||
func (db *DB) Create(stmts []string) error {
|
||||
sb := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||
|
||||
err := db.Do(func(tx *Tx) error {
|
||||
if _, err := tx.Exec(dbVersionTableDDLTmpl); err != nil {
|
||||
return errors.Wrap(err, "failed to create dbversion table")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.Do(func(tx *Tx) error {
|
||||
var version sql.NullInt64
|
||||
q, args, err := sb.Select("max(version)").From("dbversion").ToSql()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.QueryRow(q, args...).Scan(&version); err != nil {
|
||||
return errors.Wrap(err, "cannot get current db version")
|
||||
}
|
||||
if version.Valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, stmt := range stmts {
|
||||
if _, err := tx.Exec(stmt); err != nil {
|
||||
return errors.Wrapf(err, "creation failed")
|
||||
}
|
||||
}
|
||||
|
||||
q, args, err = sb.Insert("dbversion").Columns("version", "time").Values(dbVersion, "now()").ToSql()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(q, args...); err != nil {
|
||||
return errors.Wrap(err, "failed to update dbversion table")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
// Copyright 2019 Sorint.lab
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
Sqlite3 Type = "sqlite3"
|
||||
Postgres Type = "postgres"
|
||||
)
|
||||
|
||||
type dbData struct {
|
||||
t Type
|
||||
queryReplacers []replacer
|
||||
supportsTimezones bool
|
||||
}
|
||||
|
||||
type replacer struct {
|
||||
re *regexp.Regexp
|
||||
with string
|
||||
}
|
||||
|
||||
// match a postgres query bind variable. E.g. "$1", "$12", etc.
|
||||
var bindRegexp = regexp.MustCompile(`\$\d+`)
|
||||
|
||||
func matchLiteral(s string) *regexp.Regexp {
|
||||
return regexp.MustCompile(`\b` + regexp.QuoteMeta(s) + `\b`)
|
||||
}
|
||||
|
||||
var (
|
||||
dbDataPostgres = dbData{
|
||||
t: Postgres,
|
||||
supportsTimezones: true,
|
||||
queryReplacers: []replacer{
|
||||
// Remove sqlite3 only statements
|
||||
{regexp.MustCompile(`--SQLITE3\n.*`), ""},
|
||||
},
|
||||
}
|
||||
|
||||
dbDataSQLite3 = dbData{
|
||||
t: Sqlite3,
|
||||
supportsTimezones: false,
|
||||
queryReplacers: []replacer{
|
||||
{bindRegexp, "?"},
|
||||
{matchLiteral("true"), "1"},
|
||||
{matchLiteral("false"), "0"},
|
||||
{matchLiteral("boolean"), "integer"},
|
||||
{matchLiteral("bytea"), "blob"},
|
||||
// timestamp is a declared type suported by the go-sqlite3 driver
|
||||
{matchLiteral("timestamptz"), "timestamp"},
|
||||
// convert now to the max precision time available with sqlite3
|
||||
{regexp.MustCompile(`\bnow\(\)`), "strftime('%Y-%m-%d %H:%M:%f', 'now')"},
|
||||
{regexp.MustCompile(`select pg_advisory_xact_lock\(.*`), "select 1"},
|
||||
{regexp.MustCompile(`notify\s+.*`), "select 1"},
|
||||
// Remove postgres only statements
|
||||
{regexp.MustCompile(`--POSTGRES\n.*`), ""},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func (t dbData) translate(query string) string {
|
||||
for _, r := range t.queryReplacers {
|
||||
query = r.re.ReplaceAllString(query, r.with)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// translateArgs translates query parameters that may be unique to
|
||||
// a specific SQL flavor. For example, standardizing "time.Time"
|
||||
// types to UTC for clients that don't provide timezone support.
|
||||
func (t dbData) translateArgs(args []interface{}) []interface{} {
|
||||
if t.supportsTimezones {
|
||||
return args
|
||||
}
|
||||
|
||||
for i, arg := range args {
|
||||
if t, ok := arg.(time.Time); ok {
|
||||
args[i] = t.UTC()
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// DB wraps a sql.DB to add special behaviors based on the db type
|
||||
type DB struct {
|
||||
db *sql.DB
|
||||
data dbData
|
||||
}
|
||||
|
||||
func NewDB(dbType Type, dbConnString string) (*DB, error) {
|
||||
var data dbData
|
||||
var driverName string
|
||||
switch dbType {
|
||||
case Postgres:
|
||||
data = dbDataPostgres
|
||||
driverName = "postgres"
|
||||
case Sqlite3:
|
||||
data = dbDataSQLite3
|
||||
driverName = "sqlite3"
|
||||
dbConnString = "file:" + dbConnString + "?cache=shared&_journal=wal&_foreign_keys=true&_case_sensitive_like=false"
|
||||
default:
|
||||
return nil, errors.New("unknown db type")
|
||||
}
|
||||
|
||||
sqldb, err := sql.Open(driverName, dbConnString)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
db := &DB{
|
||||
db: sqldb,
|
||||
data: data,
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Tx wraps a sql.Tx to offer:
|
||||
// * apply some statement mutations before executing it
|
||||
// * locking around concurrent executions of statements (since the underlying
|
||||
// sql driver doesn't support concurrent statements on the same
|
||||
// connection/transaction)
|
||||
type Tx struct {
|
||||
db *DB
|
||||
tx *sql.Tx
|
||||
}
|
||||
|
||||
func (db *DB) Close() error {
|
||||
return db.db.Close()
|
||||
}
|
||||
|
||||
func (db *DB) Conn() (*sql.Conn, error) {
|
||||
return db.db.Conn(context.TODO())
|
||||
}
|
||||
|
||||
func (db *DB) NewUnstartedTx() *Tx {
|
||||
return &Tx{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) NewTx() (*Tx, error) {
|
||||
tx := db.NewUnstartedTx()
|
||||
if err := tx.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func (db *DB) Do(f func(tx *Tx) error) error {
|
||||
tx, err := db.NewTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
tx.Rollback()
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
if err = f(tx); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (tx *Tx) Start() error {
|
||||
wtx, err := tx.db.db.Begin()
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
switch tx.db.data.t {
|
||||
case Postgres:
|
||||
if _, err := wtx.Exec("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ"); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
tx.tx = wtx
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *Tx) Commit() error {
|
||||
if tx.tx == nil {
|
||||
return nil
|
||||
}
|
||||
return tx.tx.Commit()
|
||||
}
|
||||
|
||||
func (tx *Tx) Rollback() error {
|
||||
if tx.tx == nil {
|
||||
return nil
|
||||
}
|
||||
return tx.tx.Rollback()
|
||||
}
|
||||
|
||||
func (tx *Tx) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
query = tx.db.data.translate(query)
|
||||
r, err := tx.tx.Exec(query, tx.db.data.translateArgs(args)...)
|
||||
return r, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (tx *Tx) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
query = tx.db.data.translate(query)
|
||||
r, err := tx.tx.Query(query, tx.db.data.translateArgs(args)...)
|
||||
return r, errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (tx *Tx) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||
query = tx.db.data.translate(query)
|
||||
return tx.tx.QueryRow(query, tx.db.data.translateArgs(args)...)
|
||||
}
|
||||
|
||||
func (tx *Tx) CurTime() (time.Time, error) {
|
||||
switch tx.db.data.t {
|
||||
case Sqlite3:
|
||||
var timestring string
|
||||
if err := tx.QueryRow("select now()").Scan(×tring); err != nil {
|
||||
return time.Time{}, errors.WithStack(err)
|
||||
}
|
||||
return time.ParseInLocation("2006-01-02 15:04:05.999999999", timestring, time.UTC)
|
||||
case Postgres:
|
||||
var now time.Time
|
||||
if err := tx.QueryRow("select now()").Scan(&now); err != nil {
|
||||
return time.Time{}, errors.WithStack(err)
|
||||
}
|
||||
return now, nil
|
||||
}
|
||||
return time.Time{}, errors.New("unknown db type")
|
||||
}
|
Loading…
Reference in New Issue