99
1010 "github.com/go-sql-driver/mysql"
1111 "github.com/jinzhu/gorm"
12+ "github.com/lib/pq"
13+ "github.com/mattn/go-sqlite3"
1214 "github.com/sirupsen/logrus"
1315 "github.com/theupdateframework/notary/tuf/data"
1416)
@@ -31,7 +33,7 @@ func NewSQLStorage(dialect string, args ...interface{}) (*SQLStorage, error) {
3133}
3234
3335// translateOldVersionError captures DB errors, and attempts to translate
34- // duplicate entry - currently only supports MySQL and Sqlite3
36+ // duplicate entry
3537func translateOldVersionError (err error ) error {
3638 switch err := err .(type ) {
3739 case * mysql.MySQLError :
@@ -41,6 +43,16 @@ func translateOldVersionError(err error) error {
4143 if err .Number == 1022 || err .Number == 1062 {
4244 return ErrOldVersion {}
4345 }
46+ case pq.Error :
47+ // https://www.postgresql.org/docs/10/errcodes-appendix.html
48+ // 23505 = unique_violation
49+ if err .Code == "23505" {
50+ return ErrOldVersion {}
51+ }
52+ case sqlite3.Error :
53+ if err .ExtendedCode == sqlite3 .ErrConstraintUnique {
54+ return ErrOldVersion {}
55+ }
4456 }
4557 return err
4658}
@@ -53,8 +65,10 @@ func (db *SQLStorage) UpdateCurrent(gun data.GUN, update MetaUpdate) error {
5365 exists := db .Where ("gun = ? and role = ? and version >= ?" ,
5466 gun .String (), update .Role .String (), update .Version ).First (& TUFFile {})
5567
56- if ! exists .RecordNotFound () {
68+ if exists .Error == nil {
5769 return ErrOldVersion {}
70+ } else if ! exists .RecordNotFound () {
71+ return exists .Error
5872 }
5973
6074 // only take out the transaction once we're about to start writing
@@ -113,50 +127,59 @@ func (db *SQLStorage) getTransaction() (*gorm.DB, rollback, error) {
113127
114128// UpdateMany atomically updates many TUF records in a single transaction
115129func (db * SQLStorage ) UpdateMany (gun data.GUN , updates []MetaUpdate ) error {
130+ if ! allUpdatesUnique (updates ) {
131+ // We would fail with a unique constraint violation later, so just bail out now
132+ return ErrOldVersion {}
133+ }
134+
135+ minVersionsByRole := make (map [data.RoleName ]int )
136+ for _ , u := range updates {
137+ cur , ok := minVersionsByRole [u .Role ]
138+ if ! ok || u .Version < cur {
139+ minVersionsByRole [u .Role ] = u .Version
140+ }
141+ }
142+
143+ for role , minVersion := range minVersionsByRole {
144+ // If there are any files with version equal or higher than the minimum
145+ // version we're trying to insert, bail out now
146+ exists := db .Where ("gun = ? and role = ? and version >= ?" ,
147+ gun .String (), role .String (), minVersion ).First (& TUFFile {})
148+
149+ if exists .Error == nil {
150+ return ErrOldVersion {}
151+ } else if ! exists .RecordNotFound () {
152+ return exists .Error
153+ }
154+ }
155+
116156 tx , rb , err := db .getTransaction ()
117157 if err != nil {
118158 return err
119159 }
120- var (
121- query * gorm.DB
122- added = make (map [uint ]bool )
123- )
160+
124161 if err := func () error {
125162 for _ , update := range updates {
126- // This looks like the same logic as UpdateCurrent, but if we just
127- // called, version ordering in the updates list must be enforced
128- // (you cannot insert the version 2 before version 1). And we do
129- // not care about monotonic ordering in the updates.
130- query = db .Where ("gun = ? and role = ? and version >= ?" ,
131- gun .String (), update .Role .String (), update .Version ).First (& TUFFile {})
132-
133- if ! query .RecordNotFound () {
134- return ErrOldVersion {}
135- }
136-
137- var row TUFFile
138163 checksum := sha256 .Sum256 (update .Data )
139164 hexChecksum := hex .EncodeToString (checksum [:])
140- query = tx .Where (map [string ]interface {}{
141- "gun" : gun .String (),
142- "role" : update .Role .String (),
143- "version" : update .Version ,
144- }).Attrs ("data" , update .Data ).Attrs ("sha256" , hexChecksum ).FirstOrCreate (& row )
145-
146- if query .Error != nil {
147- return translateOldVersionError (query .Error )
148- }
149- // it's previously been added, which means it's a duplicate entry
150- // in the same transaction
151- if _ , ok := added [row .ID ]; ok {
152- return ErrOldVersion {}
165+
166+ result := tx .Create (& TUFFile {
167+ Gun : gun .String (),
168+ Role : update .Role .String (),
169+ Version : update .Version ,
170+ Data : update .Data ,
171+ SHA256 : hexChecksum ,
172+ })
173+
174+ if result .Error != nil {
175+ return translateOldVersionError (result .Error )
153176 }
177+
154178 if update .Role == data .CanonicalTimestampRole {
155179 if err := db .writeChangefeed (tx , gun , update .Version , hexChecksum ); err != nil {
156180 return err
157181 }
158182 }
159- added [row .ID ] = true
160183 }
161184 return nil
162185 }(); err != nil {
@@ -165,6 +188,22 @@ func (db *SQLStorage) UpdateMany(gun data.GUN, updates []MetaUpdate) error {
165188 return tx .Commit ().Error
166189}
167190
191+ func allUpdatesUnique (updates []MetaUpdate ) bool {
192+ type roleVersion struct {
193+ Role data.RoleName
194+ Version int
195+ }
196+ roleVersions := make (map [roleVersion ]bool )
197+ for _ , u := range updates {
198+ rv := roleVersion {u .Role , u .Version }
199+ if roleVersions [rv ] {
200+ return false
201+ }
202+ roleVersions [rv ] = true
203+ }
204+ return true
205+ }
206+
168207func (db * SQLStorage ) writeChangefeed (tx * gorm.DB , gun data.GUN , version int , checksum string ) error {
169208 c := & SQLChange {
170209 GUN : gun .String (),
0 commit comments