-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsql.go
More file actions
385 lines (346 loc) · 12.1 KB
/
sql.go
File metadata and controls
385 lines (346 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
package postgres
// Package file sql.go contains custom SQL statements that cannot be created using the SQLBoiler tool.
import (
"database/sql"
"fmt"
"strconv"
"strings"
"github.com/Defacto2/releaser"
)
type (
SQL string // SQL is a raw query statement for PostgreSQL.
Version string // Version of the PostgreSQL database server in use.
Role string // Role is a scener attribution used in the database.
)
const (
Writer Role = "(upper(credit_text))" // Writer or author of a document.
Artist Role = "(upper(credit_illustration))" // Artist or illustrator of an image or artwork.
Coder Role = "(upper(credit_program))" // Coder or programmer of a program or application.
Musician Role = "(upper(credit_audio))" // Musician or composer of a music or audio track.
)
const (
// TotalCnt is a partial SQL statement to count the number of records.
TotalCnt = "COUNT(*) AS count_total"
// SumSize is a partial SQL statement to sum the filesize values of multiple records.
SumSize = "SUM(filesize) AS size_total"
// MinYear is a partial SQL statement to select the minimum year value.
MinYear = "MIN(date_issued_year) AS min_year"
// MaxYear is a partial SQL statement to select the maximum year value.
MaxYear = "MAX(date_issued_year) AS max_year"
// Ver is a SQL statement to select the version of the PostgreSQL database server in use.
Ver = "SELECT version();"
)
// Query the database version.
func (v *Version) Query(db *sql.DB) error {
if db == nil {
return nil
}
rows, err := db.Query(Ver)
if err != nil {
return fmt.Errorf("postgres version connect query %w", err)
}
if rows.Err() != nil {
return fmt.Errorf("postgres version rows %w", rows.Err())
}
defer rows.Close()
for rows.Next() {
if err := rows.Scan(v); err != nil {
return fmt.Errorf("postgres version rows scan %w", err)
}
}
return nil
}
func (v *Version) String() string {
s := string(*v)
const invalid = 2
if x := strings.Split(s, " "); len(x) > invalid {
_, err := strconv.ParseFloat(x[1], 32)
if err != nil {
return s
}
return "and using " + strings.Join(x[0:2], " ")
}
return s
}
// Columns returns a list of column selections used for filtering and statistics.
func Columns() []string {
return []string{SumSize, TotalCnt, MinYear, MaxYear}
}
// Stat returns the SumSize and TotalCnt column selections.
func Stat() []string {
return []string{SumSize, TotalCnt}
}
const (
// releaserSEL is a partial SQL statement to select the releasers name, file count and filesize sum.
releaserSEL SQL = distReleaser +
countSum +
sumSize +
fromFiles +
combineGroup +
whereRelNull
// all SQL statements must have a tailing space,
// otherwise the statement can break when concatenated with other SQL statements.
fromFiles SQL = "FROM files "
// select distinct releaser names.
distReleaser SQL = "SELECT DISTINCT releaser, "
// count the number of files per releaser.
countSum SQL = "COUNT(files.filename) AS count_sum, "
// exclude empty releaser names.
whereRelNull SQL = "WHERE NULLIF(releaser, '') IS NOT NULL "
// releaserBy is a partial SQL statement to group the results by the releaser name.
releaserBy SQL = "GROUP BY releaser ORDER BY releaser ASC "
// only include releasers with public records.
deletedatIsNull SQL = "AND files.deletedat IS NULL "
// order the list by the oldest year and the releaser name.
orderReleaser SQL = "ORDER BY releaser ASC "
// require the releaser name to be not empty.
havingCount SQL = "HAVING (COUNT(files.filename) > 0) "
// group the results by the releaser name and deleteat.
groupbyRel SQL = "GROUP BY releaser, files.deletedat "
// exclude BBS and FTP sites.
bbsFTPRel SQL = "AND releaser !~ 'BBS\\M' AND releaser !~ 'FTP\\M' "
// require BBS sites.
bbsRel SQL = "AND releaser ~ 'BBS\\M' "
// require magazines.
magazine SQL = "AND section = 'magazine' "
// select the oldest year per releaser.
minYear SQL = "MIN(files.date_issued_year) AS min_year "
// combine the group_brand_for and group_brand_by columns as releasers.
combineGroup SQL = "CROSS JOIN LATERAL (values(group_brand_for),(group_brand_by)) AS T(releaser) "
// filter the results by the file count and the oldest year,
// this is to exclude releasers with less than 1 file or an unknown release year.
filterCountAndYear SQL = "HAVING (COUNT(files.filename) > 0) AND (MIN(files.date_issued_year) > 1970) "
// order the list by the oldest year and the releaser name.
orderSumDesc SQL = "ORDER BY count_sum DESC, releaser ASC "
// order the list by the oldest year and the releaser name.
orderMinYearRel SQL = "ORDER BY min_year ASC, releaser ASC "
// sum the filesize of files per releaser.
sumSize SQL = "SUM(files.filesize) AS size_total "
sumSizeComma SQL = "SUM(files.filesize) AS size_total, "
)
// ReleasersAlphabetical selects a list of distinct releasers or groups,
// excluding BBS and FTP sites.
func ReleasersAlphabetical() SQL {
return releaserSEL +
bbsFTPRel +
groupbyRel +
havingCount +
deletedatIsNull +
orderReleaser
}
// BBSsAlphabetical selects a list of distinct BBS names.
func BBSsAlphabetical() SQL {
return releaserSEL +
bbsRel + // require BBS sites
groupbyRel +
havingCount +
deletedatIsNull +
orderReleaser
}
// FTPsAlphabetical selects a list of distinct FTP site names.
func FTPsAlphabetical() SQL {
return releaserSEL +
"AND releaser ~ 'FTP\\M' " + // require FTP sites
groupbyRel +
havingCount +
deletedatIsNull +
orderReleaser
}
// MagazinesAlphabetical selects a list of distinct magazine titles.
func MagazinesAlphabetical() SQL {
return releaserSEL +
magazine +
groupbyRel +
havingCount +
deletedatIsNull +
orderReleaser
}
// ReleasersProlific selects a list of distinct releasers or groups,
// excluding BBS and FTP sites and ordered by the file count.
func ReleasersProlific() SQL {
return releaserSEL +
bbsFTPRel +
groupbyRel +
havingCount +
deletedatIsNull +
orderSumDesc
}
// BBSsProlific selects a list of distinct releasers or groups,
// only showing BBS sites and ordered by the file count.
func BBSsProlific() SQL {
return releaserSEL +
bbsRel + // require BBS sites
groupbyRel +
havingCount +
deletedatIsNull +
orderSumDesc
}
// ReleasersOldest selects a list of distinct releasers or groups,
// excluding BBS and FTP sites and ordered by the oldest year.
func ReleasersOldest() SQL {
return distReleaser +
countSum +
sumSizeComma +
minYear +
fromFiles +
combineGroup +
whereRelNull +
bbsFTPRel +
groupbyRel +
filterCountAndYear +
deletedatIsNull +
orderMinYearRel
}
// BBSsOldest selects a list of distinct releasers or groups,
// only showing BBS sites and ordered by the file count.
func BBSsOldest() SQL {
return distReleaser +
countSum +
sumSizeComma +
minYear +
fromFiles +
combineGroup +
whereRelNull +
bbsRel +
groupbyRel +
filterCountAndYear +
deletedatIsNull +
orderMinYearRel
}
// MagazinesOldest selects a list of distinct releasers or groups,
// only showing magazines and ordered by the file count.
func MagazinesOldest() SQL {
return distReleaser +
countSum +
sumSizeComma +
minYear +
fromFiles +
combineGroup +
whereRelNull +
magazine +
groupbyRel +
filterCountAndYear +
deletedatIsNull +
orderMinYearRel
}
// ScenerSQL is the SQL query for getting sceners.
func ScenerSQL(name string) string {
n := strings.ToUpper(releaser.Humanize(name))
exact := fmt.Sprintf("(upper(credit_text) = '%s')"+
" OR (upper(credit_program) = '%s')"+
" OR (upper(credit_illustration) = '%s')"+
" OR (upper(credit_audio) = '%s')", n, n, n, n)
first := fmt.Sprintf("(upper(credit_text) LIKE '%s,%%')"+
" OR (upper(credit_program) LIKE '%s,%%')"+
" OR (upper(credit_illustration) LIKE '%s,%%')"+
" OR (upper(credit_audio) LIKE '%s,%%')", n, n, n, n)
middle := fmt.Sprintf("(upper(credit_text) LIKE '%%,%s,%%')"+
" OR (upper(credit_program) LIKE '%%,%s,%%')"+
" OR (upper(credit_illustration) LIKE '%%,%s,%%')"+
" OR (upper(credit_audio) LIKE '%%,%s,%%')", n, n, n, n)
last := fmt.Sprintf("(upper(credit_text) LIKE '%%,%s')"+
" OR (upper(credit_program) LIKE '%%,%s')"+
" OR (upper(credit_illustration) LIKE '%%,%s')"+
" OR (upper(credit_audio) LIKE '%%,%s')", n, n, n, n)
return fmt.Sprintf("(%s) OR (%s) OR (%s) OR (%s)", exact, first, middle, last)
}
func Releasers() SQL {
return "SELECT DISTINCT releaser " +
fromFiles +
combineGroup +
whereRelNull +
"GROUP BY releaser " +
"ORDER BY releaser ASC"
}
// Summary is an SQL statement to count the number of files, sum the filesize,
// select the oldest year and the newest year.
func Summary() SQL {
return "SELECT COUNT(files.id) AS count_total, " + // count the number of files
"SUM(files.filesize) AS size_total, " + // sum the filesize of files
"MIN(files.date_issued_year) AS min_year, " + // select the oldest year
"MAX(files.date_issued_year) AS max_year " + // select the newest year
fromFiles +
"WHERE "
}
// SimilarToReleaser selects a list of distinct releasers or groups,
// like the query strings and ordered by the file count.
func SimilarToReleaser(like ...string) SQL {
query := like
for i, val := range query {
query[i] = strings.ToUpper(strings.TrimSpace(val))
}
return "SELECT * FROM (" + releaserSEL + releaserBy +
SQL(fmt.Sprintf(") sub WHERE sub.releaser SIMILAR TO '%%(%s)%%'", strings.Join(query, "|"))) +
" ORDER BY sub.count_sum DESC"
}
// SimilarToMagazine selects a list of distinct magazine titles,
// like the query strings and ordered by the file count.
func SimilarToMagazine(like ...string) SQL {
query := like
for i, val := range query {
query[i] = strings.ToUpper(strings.TrimSpace(val))
}
return "SELECT * FROM (" + releaserSEL + magazine + releaserBy +
SQL(fmt.Sprintf(") sub WHERE sub.releaser SIMILAR TO '%%(%s)%%'", strings.Join(query, "|"))) +
" ORDER BY sub.count_sum DESC"
}
// Roles returns all of the sceners reguardless of the attribution.
func Roles() Role {
s := strings.Join([]string{string(Writer), string(Artist), string(Coder), string(Musician)}, ",")
return Role(s)
}
func (r Role) Distinct() SQL {
s := "SELECT DISTINCT ON(upper(scener)) scener " + // select distinct scener names
string(fromFiles) +
// combine the Role column name as sceners
fmt.Sprintf("CROSS JOIN LATERAL (values%s) AS T(scener) ", r) +
"WHERE NULLIF(scener, '') IS NOT NULL " + // exclude empty scener names
"GROUP BY scener, files.deletedat " + // group the results by the scener name and deleteat
// filter the results by the file count and only include releasers with public records
"HAVING (COUNT(files.filename) > 0) AND files.deletedat IS NULL " +
"ORDER BY upper(scener) ASC" // order by the scener name
return SQL(s)
}
// Sceners selects a list of distinct sceners.
func Sceners() SQL {
return Roles().Distinct()
}
// Writers selects a list of distinct writers.
func Writers() SQL {
return Writer.Distinct()
}
// Artists selects a list of distinct artists.
func Artists() SQL {
return Artist.Distinct()
}
// Coders selects a list of distinct coders.
func Coders() SQL {
return Coder.Distinct()
}
// Musicians selects a list of distinct musicians.
func Musicians() SQL {
return Musician.Distinct()
}
// SumSection is an SQL statement to sum the filesizes of records matching the section.
func SumSection() SQL {
return "SELECT SUM(files.filesize) FROM files WHERE section = $1"
}
// SumGroup is an SQL statement to sum the filesizes of records matching the group.
func SumGroup() SQL {
return "SELECT SUM(filesize) as size_total FROM files WHERE group_brand_for = $1"
}
// SumPlatform is an SQL statement to sum the filesizes of records matching the platform.
func SumPlatform() SQL {
return "SELECT sum(filesize) FROM files WHERE platform = $1"
}
// SetUpper is an SQL statement to update a column with uppercase values.
func SetUpper(column string) string {
return "UPDATE files " +
fmt.Sprintf("SET %s = UPPER(%s);", column, column)
}
// SetFilesize0 is an SQL statement to update filesize column NULLs with 0 values.
// This is a fix for the error: failed to bind pointers to obj: sql:
// Scan error on column index 2, name "size_total": converting NULL to int is unsupported.
func SetFilesize0() string {
return "UPDATE files SET filesize = 0 WHERE filesize IS NULL;"
}