-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathsql_fetch.go
162 lines (140 loc) · 3.52 KB
/
sql_fetch.go
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
package imapsql
import (
"database/sql"
nettextproto "net/textproto"
"sort"
"strings"
"github.com/emersion/go-imap"
)
const flagsMidBlock = `
LEFT JOIN flags
ON flags.msgId = msgs.msgId AND msgs.mboxId = flags.mboxId`
var cachedHeaderFields = map[string]struct{}{
// Common header fields (requested by Thunderbird)
"From": {},
"To": {},
"Cc": {},
"Bcc": {},
"Subject": {},
"Date": {},
"Message-Id": {},
"Priority": {},
"X-Priority": {},
"References": {},
"Newsgroups": {},
"In-Reply-To": {},
"Content-Type": {},
"Reply-To": {},
"Importance": {},
"List-Post": {},
// Requested by Apple Mail
"X-Uniform-Type-Identifier": {},
"X-Universally-Unique-Identifier": {},
// Misc fields I think clients could be interested in.
"Sender": {},
"Return-Path": {},
"Delivered-To": {},
}
func (b *Backend) buildFetchStmt(items []imap.FetchItem) (stmt, cacheKey string, err error) {
colNames := make(map[string]struct{}, len(items)+1)
needFlags := false
colNames["msgs.msgId"] = struct{}{}
for _, item := range items {
switch item {
case imap.FetchInternalDate:
colNames["date"] = struct{}{}
case imap.FetchRFC822Size:
colNames["bodyLen"] = struct{}{}
case imap.FetchUid:
case imap.FetchEnvelope:
colNames["cachedHeader"] = struct{}{}
case imap.FetchFlags:
needFlags = true
case imap.FetchBody, imap.FetchBodyStructure:
colNames["bodyStructure"] = struct{}{}
default:
_, part, err := getNeededPart(item)
if err != nil {
return "", "", err
}
switch part {
case needCachedHeader:
colNames["cachedHeader"] = struct{}{}
case needHeader, needFullBody:
colNames["extBodyKey"] = struct{}{}
colNames["compressAlgo"] = struct{}{}
}
}
}
cols := make([]string, 0, len(colNames)+1)
for col := range colNames {
cols = append(cols, col)
}
extraParams := ""
if needFlags {
extraParams = flagsMidBlock
cols = append(cols, b.db.aggrValuesSet("flag", "{")+" AS flags")
}
sort.Strings(cols)
columns := strings.Join(cols, ", ")
return `SELECT ` + columns + `
FROM msgs
` + extraParams + `
WHERE msgs.mboxId = ? AND msgs.msgId BETWEEN ? AND ?
GROUP BY msgs.mboxId, msgs.msgId`, columns, nil
}
func (b *Backend) getFetchStmt(items []imap.FetchItem) (*sql.Stmt, error) {
str, key, err := b.buildFetchStmt(items)
if err != nil {
return nil, err
}
b.fetchStmtsLck.RLock()
stmt := b.fetchStmtsCache[key]
b.fetchStmtsLck.RUnlock()
if stmt != nil {
return stmt, nil
}
stmt, err = b.db.Prepare(str)
if err != nil {
return nil, err
}
b.fetchStmtsLck.Lock()
b.fetchStmtsCache[key] = stmt
b.fetchStmtsLck.Unlock()
return stmt, nil
}
type neededPart int
const (
needCachedHeader neededPart = iota
needHeader
needFullBody
)
func getNeededPart(item imap.FetchItem) (*imap.BodySectionName, neededPart, error) {
var sect *imap.BodySectionName
sect, err := imap.ParseBodySectionName(item)
if err != nil {
return nil, -1, err
}
onlyHeader := false
onlyCached := false
switch sect.Specifier {
case imap.MIMESpecifier, imap.HeaderSpecifier:
onlyHeader = len(sect.Path) == 0
if sect.Fields != nil && !sect.NotFields && onlyHeader {
onlyCached = true
for _, field := range sect.Fields {
cKey := nettextproto.CanonicalMIMEHeaderKey(field)
if _, ok := cachedHeaderFields[cKey]; !ok {
onlyCached = false
}
}
}
}
if onlyCached && onlyHeader {
return sect, needCachedHeader, nil
}
if !onlyHeader {
return sect, needFullBody, nil
}
return sect, needHeader, nil
}