/
stationuse.go
203 lines (174 loc) · 6.26 KB
/
stationuse.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
package types
import (
"errors"
"fmt"
"time"
"database/sql"
"github.com/gbl08ma/sqalx"
sq "github.com/Masterminds/squirrel"
)
// StationUse represents the stationUse of a Line at a certain point in time
type StationUse struct {
Station *Station
EntryTime time.Time
LeaveTime time.Time
Type StationUseType
Manual bool
SourceLine *Line
TargetLine *Line
}
// StationUseType corresponds to a type of station use (i.e. "how" the station was used)
type StationUseType string
const (
// NetworkEntry is a type of station use reserved for the first station in a trip
NetworkEntry StationUseType = "NETWORK_ENTRY"
// NetworkExit is a type of station use reserved for the last station in a trip
NetworkExit StationUseType = "NETWORK_EXIT"
// Interchange is a type of station use reserved for a line change in a trip
Interchange StationUseType = "INTERCHANGE"
// GoneThrough is a type of station use reserved for when an user simply goes through a station on his way to somewhere
GoneThrough StationUseType = "GONE_THROUGH"
// Visit is a type of station use reserved for trips with a single station use
Visit StationUseType = "VISIT"
)
// GetStationUses returns a slice with all registered stationUses
func GetStationUses(node sqalx.Node) ([]*StationUse, error) {
s := sdb.Select().
OrderBy("entry_time ASC")
return getStationUsesWithSelect(node, s)
}
// getStationUsesWithSelect returns a slice with all station uses that match the conditions in sbuilder
func getStationUsesWithSelect(node sqalx.Node, sbuilder sq.SelectBuilder) ([]*StationUse, error) {
stationUses := []*StationUse{}
tx, err := node.Beginx()
if err != nil {
return stationUses, err
}
defer tx.Commit() // read-only tx
rows, err := sbuilder.Columns("station_use.station_id", "station_use.entry_time", "station_use.leave_time",
"station_use.type", "station_use.manual", "station_use.source_line", "station_use.target_line").
From("station_use").
RunWith(tx).Query()
if err != nil {
return stationUses, fmt.Errorf("getStationUsesWithSelect: %s", err)
}
stations := []string{}
sourceLines := []string{}
targetLines := []string{}
for rows.Next() {
var stationUse StationUse
var station string
var sourceLine sql.NullString
var targetLine sql.NullString
err := rows.Scan(
&station,
&stationUse.EntryTime,
&stationUse.LeaveTime,
&stationUse.Type,
&stationUse.Manual,
&sourceLine,
&targetLine)
if err != nil {
rows.Close()
return stationUses, fmt.Errorf("getStationUsesWithSelect: %s", err)
}
stationUses = append(stationUses, &stationUse)
stations = append(stations, station)
sourceLines = append(sourceLines, sourceLine.String)
targetLines = append(targetLines, targetLine.String)
}
if err := rows.Err(); err != nil {
rows.Close()
return stationUses, fmt.Errorf("getStationUsesWithSelect: %s", err)
}
rows.Close()
for i := range stationUses {
stationUses[i].Station, err = GetStation(tx, stations[i])
if err != nil {
return stationUses, fmt.Errorf("getStationUsesWithSelect: %s", err)
}
if sourceLines[i] != "" {
stationUses[i].SourceLine, err = GetLine(tx, sourceLines[i])
if err != nil {
return stationUses, fmt.Errorf("getStationUsesWithSelect: %s", err)
}
}
if targetLines[i] != "" {
stationUses[i].TargetLine, err = GetLine(tx, targetLines[i])
if err != nil {
return stationUses, fmt.Errorf("getStationUsesWithSelect: %s", err)
}
}
}
return stationUses, nil
}
// Update adds or updates the stationUse
func (stationUse *StationUse) Update(node sqalx.Node, tripID string) error {
sourceLine, targetLine, err := stationUse.checkValidAndLines()
if err != nil {
return err
}
tx, err := node.Beginx()
if err != nil {
return err
}
defer tx.Rollback()
_, err = sdb.Insert("station_use").
Columns("trip_id", "station_id", "entry_time", "leave_time", "type", "manual", "source_line", "target_line").
Values(tripID, stationUse.Station.ID, stationUse.EntryTime, stationUse.LeaveTime, stationUse.Type, stationUse.Manual, sourceLine, targetLine).
Suffix("ON CONFLICT (trip_id, station_id, entry_time) DO UPDATE SET leave_time = ?, type = ?, manual = ?, source_line = ?, target_line = ?",
stationUse.LeaveTime, stationUse.Type, stationUse.Manual, sourceLine, targetLine).
RunWith(tx).Exec()
if err != nil {
return errors.New("AddStationUse: " + err.Error())
}
return tx.Commit()
}
func (stationUse *StationUse) checkValidAndLines() (sourceLine, targetLine sql.NullString, err error) {
if stationUse.LeaveTime.Before(stationUse.EntryTime) {
return sql.NullString{}, sql.NullString{}, errors.New("AddStationUse: leave time before entry time")
}
if stationUse.Type != NetworkEntry && stationUse.Type != NetworkExit &&
stationUse.Type != Interchange && stationUse.Type != GoneThrough &&
stationUse.Type != Visit {
return sql.NullString{}, sql.NullString{}, errors.New("AddStationUse: invalid type")
}
if stationUse.Type == Interchange &&
(stationUse.SourceLine == nil || stationUse.TargetLine == nil) {
return sql.NullString{}, sql.NullString{}, errors.New("AddStationUse: interchange use missing source or target lines")
}
if stationUse.Type != Interchange &&
(stationUse.SourceLine != nil || stationUse.TargetLine != nil) {
return sql.NullString{}, sql.NullString{}, errors.New("AddStationUse: non-interchange use contains source or target lines")
}
sourceLine = sql.NullString{
Valid: stationUse.SourceLine != nil,
}
if sourceLine.Valid {
sourceLine.String = stationUse.SourceLine.ID
}
targetLine = sql.NullString{
Valid: stationUse.TargetLine != nil,
}
if targetLine.Valid {
targetLine.String = stationUse.TargetLine.ID
}
if sourceLine.Valid && targetLine.Valid && sourceLine.String == targetLine.String {
return sql.NullString{}, sql.NullString{}, errors.New("AddStationUse: interchange has the same source and target")
}
return sourceLine, targetLine, nil
}
// Delete deletes the stationUse
func (stationUse *StationUse) Delete(node sqalx.Node, tripID string) error {
tx, err := node.Beginx()
if err != nil {
return err
}
defer tx.Rollback()
_, err = sdb.Delete("station_use").
Where(sq.Eq{"trip_id": tripID, "station_id": stationUse.Station.ID, "entry_time": stationUse.EntryTime}).RunWith(tx).Exec()
if err != nil {
return fmt.Errorf("RemoveStationUse: %s", err)
}
return tx.Commit()
}