1package repositories 2 3import ( 4 "database/sql" 5 "strings" 6 7 "github.com/pkg/errors" 8 "github.com/satori/go.uuid" 9 10 "repodiff/constants" 11 e "repodiff/entities" 12 "repodiff/interactors" 13 "repodiff/mappers" 14 repoSQL "repodiff/persistence/sql" 15 "repodiff/utils" 16) 17 18type NullCommit struct { 19 originalErr error 20} 21 22func (n NullCommit) InsertCommitRows(commitRows []e.AnalyzedCommitRow) error { 23 return n.originalErr 24} 25func (n NullCommit) GetFirstSeenTimestamp(commitHashes []string, nullTimestamp e.RepoTimestamp) (map[string]e.RepoTimestamp, error) { 26 return nil, n.originalErr 27} 28func (n NullCommit) GetMostRecentCommits() ([]e.AnalyzedCommitRow, error) { 29 return nil, n.originalErr 30} 31 32type Commit struct { 33 db *sql.DB 34 target e.MappedDiffTarget 35 timestampGenerator func() e.RepoTimestamp 36} 37 38func (c Commit) WithTimestampGenerator(t func() e.RepoTimestamp) Commit { 39 return Commit{ 40 db: c.db, 41 target: c.target, 42 timestampGenerator: t, 43 } 44} 45 46func (c Commit) InsertCommitRows(commitRows []e.AnalyzedCommitRow) error { 47 return errors.Wrap( 48 repoSQL.SingleTransactionInsert( 49 c.db, 50 `INSERT INTO project_commit ( 51 upstream_target_id, 52 downstream_target_id, 53 timestamp, 54 uuid, 55 row_index, 56 commit_, 57 downstream_project, 58 author, 59 subject, 60 project_type 61 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, 62 mappers.PrependMappedDiffTarget( 63 c.target, 64 mappers.CommitRowsToPersistCols(commitRows, c.timestampGenerator()), 65 ), 66 ), 67 "Error inserting rows into project_commit", 68 ) 69} 70 71func (c Commit) GetMostRecentOuterKey() (int64, uuid.UUID, error) { 72 var timestamp int64 73 var uuidBytes []byte 74 err := c.db.QueryRow( 75 `SELECT timestamp, uuid FROM project_commit WHERE upstream_target_id = ? AND downstream_target_id = ? AND timestamp=( 76 SELECT MAX(timestamp) FROM project_commit WHERE upstream_target_id = ? AND downstream_target_id = ? 77 ) LIMIT 1`, 78 c.target.UpstreamTarget, 79 c.target.DownstreamTarget, 80 c.target.UpstreamTarget, 81 c.target.DownstreamTarget, 82 ).Scan( 83 ×tamp, 84 &uuidBytes, 85 ) 86 if err != nil { 87 return 0, constants.NullUUID(), err 88 } 89 u, err := uuid.FromBytes(uuidBytes) 90 if err != nil { 91 return 0, constants.NullUUID(), errors.Wrap(err, "Error casting string to UUID") 92 } 93 return timestamp, u, nil 94} 95 96func (c Commit) GetMostRecentCommits() ([]e.AnalyzedCommitRow, error) { 97 timestamp, uid, err := c.GetMostRecentOuterKey() 98 if err == sql.ErrNoRows { 99 return nil, nil 100 } 101 if err != nil { 102 return nil, err 103 } 104 var errMapping error 105 106 var commitRows []e.AnalyzedCommitRow 107 var commitCursor e.AnalyzedCommitRow 108 errSelect := repoSQL.Select( 109 c.db, 110 func(row *sql.Rows) { 111 if err := interactors.ExistingErrorOr( 112 errMapping, 113 func() error { 114 commitCursor, err = mappers.SQLRowToCommitRow(row) 115 return err 116 }, 117 ); err != nil { 118 errMapping = err 119 return 120 } 121 commitRows = append( 122 commitRows, 123 commitCursor, 124 ) 125 }, 126 `SELECT 127 timestamp, 128 uuid, 129 row_index, 130 commit_, 131 downstream_project, 132 author, 133 subject, 134 project_type 135 FROM project_commit 136 WHERE 137 upstream_target_id = ? 138 AND downstream_target_id = ? 139 AND timestamp = ? 140 AND uuid = ?`, 141 c.target.UpstreamTarget, 142 c.target.DownstreamTarget, 143 timestamp, 144 string(uid.Bytes()), 145 ) 146 if err := interactors.AnyError(errSelect, errMapping); err != nil { 147 return nil, err 148 } 149 return commitRows, nil 150} 151 152func (c Commit) GetFirstSeenTimestamp(commitHashes []string, nullTimestamp e.RepoTimestamp) (map[string]e.RepoTimestamp, error) { 153 if len(commitHashes) == 0 { 154 return map[string]e.RepoTimestamp{}, nil 155 } 156 commitToTimestamp := make(map[string]e.RepoTimestamp, len(commitHashes)) 157 var commitCursor string 158 var timestampCursor e.RepoTimestamp 159 var errMapping error 160 161 errSelect := repoSQL.Select( 162 c.db, 163 func(row *sql.Rows) { 164 if err := interactors.ExistingErrorOr( 165 errMapping, 166 func() error { 167 return row.Scan( 168 &commitCursor, 169 ×tampCursor, 170 ) 171 }, 172 ); err != nil { 173 errMapping = err 174 return 175 } 176 commitToTimestamp[commitCursor] = timestampCursor 177 }, 178 `SELECT commit_, MIN(timestamp) 179 FROM project_commit 180 WHERE upstream_target_id = ? 181 AND downstream_target_id = ? 182 AND commit_ IN(?`+strings.Repeat(",?", len(commitHashes)-1)+`) 183 GROUP BY commit_ 184 `, 185 append( 186 []interface{}{ 187 c.target.UpstreamTarget, 188 c.target.DownstreamTarget, 189 }, 190 asInterfaceSlice(commitHashes)..., 191 )..., 192 ) 193 if err := interactors.AnyError(errSelect, errMapping); err != nil { 194 return nil, err 195 } 196 mutateEmptyValues(commitToTimestamp, commitHashes, nullTimestamp) 197 return commitToTimestamp, nil 198} 199 200func NewCommitRepository(target e.MappedDiffTarget) (Commit, error) { 201 db, err := repoSQL.GetDBConnectionPool() 202 return Commit{ 203 db: db, 204 target: target, 205 timestampGenerator: utils.TimestampSeconds, 206 }, errors.Wrap(err, "Could not establish a database connection") 207} 208 209func NewNullObject(originalErr error) NullCommit { 210 return NullCommit{ 211 originalErr: originalErr, 212 } 213} 214 215func mutateEmptyValues(existing map[string]e.RepoTimestamp, shouldExist []string, defaultValue e.RepoTimestamp) { 216 for _, key := range shouldExist { 217 if _, ok := existing[key]; !ok { 218 existing[key] = defaultValue 219 } 220 } 221} 222 223func asInterfaceSlice(strings []string) []interface{} { 224 casted := make([]interface{}, len(strings)) 225 for i, s := range strings { 226 casted[i] = s 227 } 228 return casted 229} 230