Monorepo for Tangled
tangled.org
1package db
2
3import (
4 "database/sql"
5 "fmt"
6 "strings"
7 "time"
8
9 "github.com/bluesky-social/indigo/atproto/syntax"
10 "tangled.org/core/appview/models"
11 "tangled.org/core/appview/pagination"
12)
13
14// NewPullRound creates new PR submission.
15// Set pullId to open a new PR.
16func NewPullRound(tx *sql.Tx, pullId int, round *models.PullRound) error {
17 // Create new PR when pullId isn't set
18 if pullId == 0 {
19 // ensure sequence exists
20 _, err := tx.Exec(`
21 insert or ignore into repo_pull_seqs (repo_at, next_pull_id)
22 values (?, 1)
23 `, round.Target.RepoAt)
24 if err != nil {
25 return err
26 }
27
28 err = tx.QueryRow(`
29 update repo_pull_seqs
30 set next_pull_id = next_pull_id + 1
31 where repo_at = ?
32 returning next_pull_id - 1
33 `, round.Target.RepoAt).Scan(&pullId)
34 if err != nil {
35 return err
36 }
37
38 _, err = tx.Exec(
39 `insert into pulls2 (pull_id, did, rkey, state)
40 values (?, ?, ?, ?)`,
41 pullId,
42 round.Did,
43 round.Rkey,
44 models.PullOpen,
45 )
46 if err != nil {
47 return fmt.Errorf("insert pull submission: %w", err)
48 }
49 }
50
51 var sourceRepoAt, sourceBranch *string
52 if round.Source != nil {
53 x := round.Source.RepoAt.String()
54 sourceRepoAt = &x
55 sourceBranch = &round.Source.Branch
56 }
57 _, err := tx.Exec(
58 `insert into pull_rounds (
59 pull_did,
60 pull_rkey,
61 cid,
62 target_repo_at,
63 target_branch,
64 source_repo_at,
65 source_branch,
66 patch,
67 title,
68 body,
69 created
70 )
71 values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
72 round.Did,
73 round.Rkey,
74 round.Cid,
75 round.Target.RepoAt,
76 round.Target.Branch,
77 sourceRepoAt,
78 sourceBranch,
79 round.Patch,
80 round.Title,
81 round.Body,
82 round.Created.Format(time.RFC3339),
83 )
84 if err != nil {
85 return fmt.Errorf("insert pull submission: %w", err)
86 }
87
88 err = tx.QueryRow(
89 `select id, round from pull_rounds_view
90 where did = ? and rkey = ? and cid = ?`,
91 round.Did, round.Rkey, round.Cid,
92 ).Scan(&round.Id, &round.Round)
93 if err != nil {
94 return fmt.Errorf("get id and round number: %w", err)
95 }
96
97 if err := putReferences(tx, round.AtUri(), &round.Cid, round.References); err != nil {
98 return fmt.Errorf("put reference_links: %w", err)
99 }
100
101 return nil
102}
103
104func SetPullState2(e Execer, pullAt syntax.ATURI, state models.PullState) error {
105 _, err := e.Exec(
106 `update pulls2 set state = ? where at_uri = ? and (state <> ? or state <> ?)`,
107 state,
108 pullAt,
109 models.PullDeleted, // only update state of non-deleted pulls
110 models.PullMerged, // only update state of non-merged pulls
111 )
112 return err
113}
114
115func ClosePull2(e Execer, pullAt syntax.ATURI) error {
116 return SetPullState2(e, pullAt, models.PullClosed)
117}
118
119func ReopenPull2(e Execer, pullAt syntax.ATURI) error {
120 return SetPullState2(e, pullAt, models.PullOpen)
121}
122
123func MergePull2(e Execer, pullAt syntax.ATURI) error {
124 return SetPullState2(e, pullAt, models.PullMerged)
125}
126
127func DeletePull2(e Execer, pullAt syntax.ATURI) error {
128 return SetPullState2(e, pullAt, models.PullDeleted)
129}
130
131func GetPulls2(e Execer, filters ...filter) ([]*models.Pull2, error) {
132 return GetPullsPaginated(e, pagination.Page{}, filters...)
133}
134
135func GetPullsPaginated(e Execer, page pagination.Page, filters ...filter) ([]*models.Pull2, error) {
136 pullsMap := make(map[syntax.ATURI]*models.Pull2)
137
138 var conditions []string
139 var args []any
140 for _, filter := range filters {
141 conditions = append(conditions, filter.Condition())
142 args = append(args, filter.Arg()...)
143 }
144
145 whereClause := ""
146 if conditions != nil {
147 whereClause = " where " + strings.Join(conditions, " and ")
148 }
149
150 pLower := FilterGte("row_num", page.Offset+1)
151 pUpper := FilterLte("row_num", page.Offset+page.Limit)
152
153 pageClause := ""
154 if page.Limit > 0 {
155 args = append(args, pLower.Arg()...)
156 args = append(args, pUpper.Arg()...)
157 pageClause = " where " + pLower.Condition() + " and " + pUpper.Condition()
158 }
159
160 query := fmt.Sprintf(
161 `select * from (
162 select
163 id,
164 did,
165 rkey,
166 cid,
167 target_repo_at,
168 target_branch,
169 source_repo_at,
170 source_branch,
171 patch,
172 title,
173 body,
174 created,
175 row_number() over (order by id desc) as row_num
176 from
177 pull_rounds
178 %s
179 ) ranked_pull_rounds
180 %s`,
181 whereClause,
182 pageClause,
183 )
184
185 rows, err := e.Query(query, args...)
186 if err != nil {
187 return nil, fmt.Errorf("query pulls table: %w", err)
188 }
189 defer rows.Close()
190
191 for rows.Next() {
192 var round models.PullRound
193 var sourceBranch, sourceRepoAt sql.NullString
194 var created string
195 err := rows.Scan(
196 &round.Id,
197 &round.Did,
198 &round.Rkey,
199 &round.Cid,
200 &round.Target.RepoAt,
201 &round.Target.Branch,
202 &sourceBranch,
203 &sourceRepoAt,
204 &round.Patch,
205 &round.Title,
206 &round.Body,
207 &created,
208 )
209 if err != nil {
210 return nil, fmt.Errorf("scan row: %w", err)
211 }
212
213 if sourceBranch.Valid && sourceRepoAt.Valid {
214 round.Source = &models.PullSource2{}
215 round.Source.Branch = sourceBranch.String
216 round.Source.RepoAt = syntax.ATURI(sourceRepoAt.String)
217 }
218 createdAtTime, _ := time.Parse(time.RFC3339, created)
219 round.Created = createdAtTime
220
221 pull, ok := pullsMap[round.AtUri()]
222 if !ok {
223 pull = &models.Pull2{
224 Did: round.Did,
225 Rkey: round.Rkey,
226 }
227 }
228 pull.Submissions = append(pull.Submissions, &round)
229 pullsMap[round.AtUri()] = pull
230 }
231
232 // TODO: fetch pulls (id, pull_id, state) from (did, rkey)
233
234 panic("unimplemented")
235}