[#316] locode/csv: Scan subdivision csv-table into memory

Scanning subdivision csv-table entries one-by-one takes significant time and
system resources. To speed up random access to table records, on the first
call, the table is pumped into memory (map). On subsequent calls, I/O
operations are not performed.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2021-02-10 21:06:00 +03:00 committed by Alex Vanin
parent 307355f165
commit 977229eb5a
2 changed files with 57 additions and 16 deletions

View file

@ -44,26 +44,62 @@ func (t *Table) IterateAll(f func(locode.Record) error) error {
})
}
// SubDivName scans table record one-by-one, and returns subdivision name
// on country and subdivision codes match.
const (
_ = iota - 1
subDivCountry
subDivSubdivision
subDivName
_ // subDivLevel
subDivFldNum
)
type subDivKey struct {
countryCode,
subDivCode string
}
type subDivRecord struct {
name string
}
// SubDivName scans table record to in-memory table (once),
// and returns subdivision name on country and subdivision codes match.
//
// Returns locodedb.ErrSubDivNotFound if no entry matches.
func (t *Table) SubDivName(countryCode *locodedb.CountryCode, name string) (subDiv string, err error) {
const wordsPerRecord = 4
err = t.scanWords([]string{t.subDivPath}, wordsPerRecord, func(words []string) error {
if words[0] == countryCode.String() && words[1] == name {
subDiv = words[2]
return errScanInt
}
return nil
})
if err == nil && subDiv == "" {
err = locodedb.ErrSubDivNotFound
func (t *Table) SubDivName(countryCode *locodedb.CountryCode, code string) (string, error) {
if err := t.initSubDiv(); err != nil {
return "", err
}
rec, ok := t.mSubDiv[subDivKey{
countryCode: countryCode.String(),
subDivCode: code,
}]
if !ok {
return "", locodedb.ErrSubDivNotFound
}
return rec.name, nil
}
func (t *Table) initSubDiv() (err error) {
t.subDivOnce.Do(func() {
t.mSubDiv = make(map[subDivKey]subDivRecord)
err = t.scanWords([]string{t.subDivPath}, subDivFldNum, func(words []string) error {
t.mSubDiv[subDivKey{
countryCode: words[subDivCountry],
subDivCode: words[subDivSubdivision],
}] = subDivRecord{
name: words[subDivName],
}
return nil
})
})
return
}

View file

@ -3,6 +3,7 @@ package csvlocode
import (
"fmt"
"os"
"sync"
)
// Prm groups the required parameters of the Table's constructor.
@ -34,6 +35,10 @@ type Table struct {
mode os.FileMode
subDivPath string
subDivOnce sync.Once
mSubDiv map[subDivKey]subDivRecord
}
const invalidPrmValFmt = "invalid parameter %s (%T):%v"