Skip to content

Commit 29077df

Browse files
committed
WIP
1 parent 7161612 commit 29077df

File tree

4 files changed

+103
-9
lines changed

4 files changed

+103
-9
lines changed

internal/decoder/decoder.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ import (
1212
"github.com/oschwald/maxminddb-golang/v2/internal/mmdberrors"
1313
)
1414

15+
type Cacher interface {
16+
Load(key uint) (any, bool)
17+
Store(key uint, value any)
18+
}
19+
1520
type Decoder struct {
1621
buffer []byte
22+
cache Cacher
1723
}
1824

1925
type dataType int
@@ -44,8 +50,8 @@ const (
4450
maximumDataStructureDepth = 512
4551
)
4652

47-
func New(buffer []byte) Decoder {
48-
return Decoder{buffer: buffer}
53+
func New(buffer []byte, cache Cacher) Decoder {
54+
return Decoder{buffer: buffer, cache: cache}
4955
}
5056

5157
func (d *Decoder) Decode(offset uint, v any) error {
@@ -348,7 +354,10 @@ func (d *Decoder) decodeFromTypeToDeserializer(
348354
v, offset := d.decodeInt(size, offset)
349355
return offset, dser.Int32(int32(v))
350356
case _String:
351-
v, offset := d.decodeString(size, offset)
357+
v, offset, err := d.decodeString(size, offset)
358+
if err != nil {
359+
return 0, err
360+
}
352361
return offset, dser.String(v)
353362
case _Uint16:
354363
v, offset := d.decodeUint(size, offset)
@@ -581,7 +590,10 @@ func (d *Decoder) unmarshalSlice(
581590
}
582591

583592
func (d *Decoder) unmarshalString(size, offset uint, result reflect.Value) (uint, error) {
584-
value, newOffset := d.decodeString(size, offset)
593+
value, newOffset, err := d.decodeString(size, offset)
594+
if err != nil {
595+
return 0, err
596+
}
585597

586598
switch result.Kind() {
587599
case reflect.String:
@@ -838,9 +850,25 @@ func (d *Decoder) decodeSliceToDeserializer(
838850
return offset, nil
839851
}
840852

841-
func (d *Decoder) decodeString(size, offset uint) (string, uint) {
853+
func (d *Decoder) decodeString(size, offset uint) (string, uint, error) {
842854
newOffset := offset + size
843-
return string(d.buffer[offset:newOffset]), newOffset
855+
if d.cache == nil {
856+
return string(d.buffer[offset:newOffset]), newOffset, nil
857+
}
858+
859+
v, ok := d.cache.Load(offset)
860+
if ok {
861+
if s, ok := v.(string); ok {
862+
return s, newOffset, nil
863+
}
864+
return "", 0, mmdberrors.NewCacheTypeStrError(v, "string")
865+
}
866+
867+
s := string(d.buffer[offset:newOffset])
868+
869+
d.cache.Store(offset, s)
870+
871+
return s, newOffset, nil
844872
}
845873

846874
func (d *Decoder) decodeStruct(

internal/mmdberrors/errors.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,22 @@ func (e InvalidDatabaseError) Error() string {
2323
return e.message
2424
}
2525

26+
type CacheTypeError struct {
27+
Type string
28+
Value any
29+
}
30+
31+
func NewCacheTypeStrError(value any, expType string) CacheTypeError {
32+
return CacheTypeError{
33+
Type: expType,
34+
Value: value,
35+
}
36+
}
37+
38+
func (e CacheTypeError) Error() string {
39+
return fmt.Sprintf("maxminddb: expected %s type in cache but found %T", e.Type, e.Value)
40+
}
41+
2642
// UnmarshalTypeError is returned when the value in the database cannot be
2743
// assigned to the specified data type.
2844
type UnmarshalTypeError struct {

reader.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,18 @@ type Metadata struct {
5050
RecordSize uint `maxminddb:"record_size"`
5151
}
5252

53-
type readerOptions struct{}
53+
type readerOptions struct {
54+
cache decoder.Cacher
55+
}
5456

5557
type ReaderOption func(*readerOptions)
5658

59+
func Cache(cache decoder.Cacher) ReaderOption {
60+
return func(o *readerOptions) {
61+
o.cache = cache
62+
}
63+
}
64+
5765
// Open takes a string path to a MaxMind DB file and any options. It returns a
5866
// Reader structure or an error. The database file is opened using a memory
5967
// map on supported platforms. On platforms without memory map support, such
@@ -143,7 +151,7 @@ func FromBytes(buffer []byte, options ...ReaderOption) (*Reader, error) {
143151
}
144152

145153
metadataStart += len(metadataStartMarker)
146-
metadataDecoder := decoder.New(buffer[metadataStart:])
154+
metadataDecoder := decoder.New(buffer[metadataStart:], nil)
147155

148156
var metadata Metadata
149157

@@ -159,7 +167,8 @@ func FromBytes(buffer []byte, options ...ReaderOption) (*Reader, error) {
159167
return nil, mmdberrors.NewInvalidDatabaseError("the MaxMind DB contains invalid metadata")
160168
}
161169
d := decoder.New(
162-
buffer[searchTreeSize+dataSectionSeparatorSize : metadataStart-len(metadataStartMarker)],
170+
buffer[searchTreeSize+dataSectionSeparatorSize:metadataStart-len(metadataStartMarker)],
171+
opts.cache,
163172
)
164173

165174
nodeBuffer := buffer[:searchTreeSize]

reader_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,44 @@ func BenchmarkCityLookup(b *testing.B) {
931931
var result fullCity
932932

933933
s := make(net.IP, 4)
934+
935+
b.ResetTimer()
936+
937+
for range b.N {
938+
ip := randomIPv4Address(r, s)
939+
err = db.Lookup(ip).Decode(&result)
940+
if err != nil {
941+
b.Error(err)
942+
}
943+
}
944+
require.NoError(b, db.Close(), "error on close")
945+
}
946+
947+
type cache struct {
948+
m map[uint]any
949+
}
950+
951+
func (c *cache) Load(key uint) (any, bool) {
952+
v, ok := c.m[key]
953+
return v, ok
954+
}
955+
956+
func (c *cache) Store(key uint, value any) {
957+
c.m[key] = value
958+
}
959+
960+
func BenchmarkCityLookupWithCache(b *testing.B) {
961+
db, err := Open("GeoLite2-City.mmdb", Cache(&cache{m: map[uint]any{}}))
962+
require.NoError(b, err)
963+
964+
//nolint:gosec // this is a test
965+
r := rand.New(rand.NewSource(time.Now().UnixNano()))
966+
var result fullCity
967+
968+
s := make(net.IP, 4)
969+
970+
b.ResetTimer()
971+
934972
for range b.N {
935973
ip := randomIPv4Address(r, s)
936974
err = db.Lookup(ip).Decode(&result)
@@ -949,6 +987,9 @@ func BenchmarkCityLookupOnly(b *testing.B) {
949987
r := rand.New(rand.NewSource(time.Now().UnixNano()))
950988

951989
s := make(net.IP, 4)
990+
991+
b.ResetTimer()
992+
952993
for range b.N {
953994
ip := randomIPv4Address(r, s)
954995
result := db.Lookup(ip)

0 commit comments

Comments
 (0)