-
Notifications
You must be signed in to change notification settings - Fork 2
/
lookup.go
229 lines (206 loc) · 5.23 KB
/
lookup.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
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
package rtfinder
import (
"sync"
"time"
"github.com/interline-io/log"
"github.com/jmoiron/sqlx"
)
type lookupCache struct {
db sqlx.Ext
fvidSourceCache simpleCache[int, []string]
fvidFeedCache simpleCache[int, string]
gtfsTripIdCache simpleCache[int, string]
gtfsStopIdCache simpleCache[int, string]
routeIdCache simpleCache[skey, int]
tzCache tzCache
rtLookupLock sync.Mutex
}
func newLookupCache(db sqlx.Ext) *lookupCache {
return &lookupCache{
db: db,
}
}
func (f *lookupCache) GetRouteID(fvid int, tid string) (int, bool) {
sk := skey{fvid, tid}
if a, ok := f.routeIdCache.Get(sk); ok {
return a, ok
}
eid := 0
err := sqlx.Get(f.db, &eid, "select id from gtfs_routes where feed_version_id = $1 and route_id = $2", fvid, tid)
f.routeIdCache.Set(sk, eid)
return eid, err == nil
}
func (f *lookupCache) GetGtfsTripID(id int) (string, bool) {
if a, ok := f.gtfsTripIdCache.Get(id); ok {
return a, ok
}
q := `select trip_id from gtfs_trips where id = $1 limit 1`
eid := ""
err := sqlx.Get(f.db, &eid, q, id)
f.gtfsTripIdCache.Set(id, eid)
return eid, err == nil
}
func (f *lookupCache) GetGtfsStopID(id int) (string, bool) {
if a, ok := f.gtfsStopIdCache.Get(id); ok {
return a, ok
}
q := `select stop_id from gtfs_stops where id = $1 limit 1`
eid := ""
err := sqlx.Get(f.db, &eid, q, id)
f.gtfsStopIdCache.Set(id, eid)
return eid, err == nil
}
func (f *lookupCache) GetFeedVersionRTFeeds(id int) ([]string, bool) {
f.rtLookupLock.Lock()
defer f.rtLookupLock.Unlock()
if a, ok := f.fvidSourceCache.Get(id); ok {
return a, ok
}
q := `
select
distinct on(cf.onestop_id)
cf.onestop_id
from feed_versions fv
join current_operators_in_feed coif on coif.feed_id = fv.feed_id
join current_operators_in_feed coif2 on coif2.resolved_onestop_id = coif.resolved_onestop_id
join current_feeds cf on coif2.feed_id = cf.id
where fv.id = $1
order by cf.onestop_id
`
var eid []string
err := sqlx.Select(
f.db,
&eid,
q,
id,
)
f.fvidSourceCache.Set(id, eid) // set before return
if err != nil {
return nil, false
}
return eid, true
}
// StopTimezone looks up the timezone for a stop
func (f *lookupCache) StopTimezone(id int, known string) (*time.Location, bool) {
// If a timezone is provided, save it and return immediately
if known != "" {
// log.Trace().Int("stop_id", id).Str("known", known).Msg("tz: using known timezone")
return f.tzCache.Add(id, known)
}
// Check the cache
if loc, ok := f.tzCache.Get(id); ok {
// log.Trace().Int("stop_id", id).Str("known", known).Str("loc", loc.String()).Msg("tz: using cached timezone")
return loc, ok
}
if id == 0 {
log.Trace().Int("stop_id", id).Msg("tz: lookup failed, cant find timezone for stops with id=0 unless speciifed explicitly")
return nil, false
}
// Otherwise lookup the timezone
q := `
select COALESCE(nullif(s.stop_timezone, ''), nullif(p.stop_timezone, ''), a.agency_timezone)
from gtfs_stops s
left join gtfs_stops p on p.id = s.parent_station
left join lateral (
select gtfs_agencies.agency_timezone
from gtfs_agencies
where gtfs_agencies.feed_version_id = s.feed_version_id
limit 1
) a on true
where s.id = $1
limit 1`
tz := ""
if err := sqlx.Get(f.db, &tz, q, id); err != nil {
log.Error().Err(err).Int("stop_id", id).Str("known", known).Msg("tz: lookup failed")
return nil, false
}
loc, ok := f.tzCache.Add(id, tz)
log.Trace().Int("stop_id", id).Str("known", known).Str("loc", loc.String()).Msg("tz: lookup successful")
return loc, ok
}
// Lookup time.Location by name
func (f *lookupCache) Location(tz string) (*time.Location, bool) {
return f.tzCache.Location(tz)
}
/////
type skey struct {
fvid int
eid string
}
///
type simpleCache[K comparable, V any] struct {
lock sync.Mutex
values map[K]V
}
func (c *simpleCache[K, V]) Get(key K) (V, bool) {
c.lock.Lock()
defer c.lock.Unlock()
a, ok := c.values[key]
return a, ok
}
func (c *simpleCache[K, V]) Set(key K, value V) {
c.lock.Lock()
defer c.lock.Unlock()
if c.values == nil {
c.values = map[K]V{}
}
c.values[key] = value
}
func newSimpleCache[K comparable, V any]() *simpleCache[K, V] {
return &simpleCache[K, V]{
values: map[K]V{},
}
}
////
// tzCache saves and manages the timezone location cache
type tzCache struct {
lock sync.Mutex
tzs map[string]*time.Location
values map[int]string
}
func newTzCache() *tzCache {
return &tzCache{
tzs: map[string]*time.Location{},
values: map[int]string{},
}
}
func (c *tzCache) Get(key int) (*time.Location, bool) {
var loc *time.Location
defer c.lock.Unlock()
c.lock.Lock()
tz, ok := c.values[key]
if ok {
loc, ok = c.tzs[tz] // will be nil if invalid timezone
}
return loc, ok
}
func (c *tzCache) Location(tz string) (*time.Location, bool) {
defer c.lock.Unlock()
c.lock.Lock()
loc := c.tzs[tz]
return loc, loc == nil
}
func (c *tzCache) Add(key int, tz string) (*time.Location, bool) {
var err error
var loc *time.Location
c.lock.Lock()
defer c.lock.Unlock()
if c.values == nil {
c.values = map[int]string{}
}
if c.tzs == nil {
c.tzs = map[string]*time.Location{}
}
c.values[key] = tz
loc, ok := c.tzs[tz]
if !ok {
ok = true
loc, err = time.LoadLocation(tz)
if err != nil {
ok = false
loc = nil
}
c.tzs[tz] = loc
}
return loc, ok
}