Commit 84598fe1 authored by Kirill Smelkov's avatar Kirill Smelkov Committed by Kamil Kisiel

Add support for persistent references (#37)

* Add docstrings to types exposed in public API

I was refreshing my knowled of ogórek and added them along the way.

* Add support for persistent references

Python has the mechanism for objects in one pickle to reference objects
in another pickle. This mechanism is primarily used in ZODB object
database for one object to "persistently" reference another object(*).

I needed a way to load and understand objects from a ZODB database in
Go, and hence comes this patch for ogórek that teaches it to understand
such persistent references.

Like it is already with Call and Class we add a dedicated Ref type to
represent persistent references in pickle stream.

(*) in ZODB when one object is changed, there is no need to resave
unchanged object that is referencing it because its reference stays
valid and pointing to changed object: the "persistent ID" in ZODB is
only object ID and which revision of the referenced object it points to
is implicitly follows by current transaction number view of the database.
parent dd41cde7
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"math" "math"
"math/big" "math/big"
"reflect" "reflect"
"strings"
) )
type TypeError struct { type TypeError struct {
...@@ -299,6 +300,21 @@ func (e *Encoder) encodeClass(v *Class) error { ...@@ -299,6 +300,21 @@ func (e *Encoder) encodeClass(v *Class) error {
return err return err
} }
func (e *Encoder) encodeRef(v *Ref) error {
if pids, ok := v.Pid.(string); ok && !strings.Contains(pids, "\n") {
_, err := fmt.Fprintf(e.w, "%c%s\n", opPersid, pids)
return err
} else {
// XXX we can use opBinpersid only if .protocol >= 1
err := e.encode(reflectValueOf(v.Pid))
if err != nil {
return err
}
_, err = e.w.Write([]byte{opBinpersid})
return err
}
}
func (e *Encoder) encodeStruct(st reflect.Value) error { func (e *Encoder) encodeStruct(st reflect.Value) error {
typ := st.Type() typ := st.Type()
...@@ -312,6 +328,8 @@ func (e *Encoder) encodeStruct(st reflect.Value) error { ...@@ -312,6 +328,8 @@ func (e *Encoder) encodeStruct(st reflect.Value) error {
return e.encodeCall(&v) return e.encodeCall(&v)
case Class: case Class:
return e.encodeClass(&v) return e.encodeClass(&v)
case Ref:
return e.encodeRef(&v)
case big.Int: case big.Int:
return e.encodeLong(&v) return e.encodeLong(&v)
} }
......
...@@ -90,6 +90,7 @@ var ErrInvalidPickleVersion = errors.New("invalid pickle version") ...@@ -90,6 +90,7 @@ var ErrInvalidPickleVersion = errors.New("invalid pickle version")
var errNoMarker = errors.New("no marker in stack") var errNoMarker = errors.New("no marker in stack")
var errStackUnderflow = errors.New("pickle: stack underflow") var errStackUnderflow = errors.New("pickle: stack underflow")
// OpcodeError is the error that Decode returns when it sees unknown pickle opcode.
type OpcodeError struct { type OpcodeError struct {
Key byte Key byte
Pos int Pos int
...@@ -468,16 +469,43 @@ func (d *Decoder) loadNone() error { ...@@ -468,16 +469,43 @@ func (d *Decoder) loadNone() error {
return nil return nil
} }
// Ref represents Python's persistent reference.
//
// Such references are used when one pickle somehow references another pickle
// in e.g. a database.
//
// See https://docs.python.org/3/library/pickle.html#pickle-persistent for details.
type Ref struct {
// persistent ID of referenced object.
//
// used to be string for protocol 0, but "upgraded" to be arbitrary
// object for later protocols.
Pid interface{}
}
// Push a persistent object id // Push a persistent object id
func (d *Decoder) loadPersid() error { func (d *Decoder) loadPersid() error {
return errNotImplemented pid, err := d.readLine()
if err != nil {
return err
}
d.push(Ref{Pid: string(pid)})
return nil
} }
// Push a persistent object id from items on the stack // Push a persistent object id from items on the stack
func (d *Decoder) loadBinPersid() error { func (d *Decoder) loadBinPersid() error {
return errNotImplemented pid, err := d.pop()
if err != nil {
return err
}
d.push(Ref{Pid: pid})
return nil
} }
// Call represents Python's call.
type Call struct { type Call struct {
Callable Class Callable Class
Args Tuple Args Tuple
...@@ -645,6 +673,7 @@ func (d *Decoder) build() error { ...@@ -645,6 +673,7 @@ func (d *Decoder) build() error {
return errNotImplemented return errNotImplemented
} }
// Class represents a Python class.
type Class struct { type Class struct {
Module, Name string Module, Name string
} }
......
...@@ -62,6 +62,9 @@ var tests = []struct { ...@@ -62,6 +62,9 @@ var tests = []struct {
{"dict with strings", "(dp0\nS'a'\np1\nS'1'\np2\nsS'b'\np3\nS'2'\np4\ns.", map[interface{}]interface{}{"a": "1", "b": "2"}}, {"dict with strings", "(dp0\nS'a'\np1\nS'1'\np2\nsS'b'\np3\nS'2'\np4\ns.", map[interface{}]interface{}{"a": "1", "b": "2"}},
{"GLOBAL and REDUCE opcodes", "cfoo\nbar\nS'bing'\n\x85R.", Call{Callable: Class{Module: "foo", Name: "bar"}, Args: []interface{}{"bing"}}}, {"GLOBAL and REDUCE opcodes", "cfoo\nbar\nS'bing'\n\x85R.", Call{Callable: Class{Module: "foo", Name: "bar"}, Args: []interface{}{"bing"}}},
{"LONG_BINPUT opcode", "(lr0000I17\na.", []interface{}{int64(17)}}, {"LONG_BINPUT opcode", "(lr0000I17\na.", []interface{}{int64(17)}},
{"persistent ref", "Pabc\n.", Ref{"abc"}},
{"bin persistent ref", "\x80\x01U\x05def\ngQ.", Ref{"def\ng"}},
{"bin persistent ref to !string", "\x80\x01(I1\nI2\ntQ.", Ref{Tuple{int64(1), int64(2)}}},
{"graphite message1", string(graphitePickle1), []interface{}{map[interface{}]interface{}{"values": []interface{}{float64(473), float64(497), float64(540), float64(1497), float64(1808), float64(1890), float64(2013), float64(1821), float64(1847), float64(2176), float64(2156), float64(1250), float64(2055), float64(1570), None{}, None{}}, "start": int64(1383782400), "step": int64(86400), "end": int64(1385164800), "name": "ZZZZ.UUUUUUUU.CCCCCCCC.MMMMMMMM.XXXXXXXXX.TTT"}}}, {"graphite message1", string(graphitePickle1), []interface{}{map[interface{}]interface{}{"values": []interface{}{float64(473), float64(497), float64(540), float64(1497), float64(1808), float64(1890), float64(2013), float64(1821), float64(1847), float64(2176), float64(2156), float64(1250), float64(2055), float64(1570), None{}, None{}}, "start": int64(1383782400), "step": int64(86400), "end": int64(1385164800), "name": "ZZZZ.UUUUUUUU.CCCCCCCC.MMMMMMMM.XXXXXXXXX.TTT"}}},
{"graphite message2", string(graphitePickle2), []interface{}{map[interface{}]interface{}{"values": []interface{}{float64(473), float64(497), float64(540), float64(1497), float64(1808), float64(1890), float64(2013), float64(1821), float64(1847), float64(2176), float64(2156), float64(1250), float64(2055), float64(1570), None{}, None{}}, "start": int64(1383782400), "step": int64(86400), "end": int64(1385164800), "name": "user.login.area.machine.metric.minute"}}}, {"graphite message2", string(graphitePickle2), []interface{}{map[interface{}]interface{}{"values": []interface{}{float64(473), float64(497), float64(540), float64(1497), float64(1808), float64(1890), float64(2013), float64(1821), float64(1847), float64(2176), float64(2156), float64(1250), float64(2055), float64(1570), None{}, None{}}, "start": int64(1383782400), "step": int64(86400), "end": int64(1385164800), "name": "user.login.area.machine.metric.minute"}}},
{"graphite message3", string(graphitePickle3), []interface{}{map[interface{}]interface{}{"intervals": []interface{}{}, "metric_path": "carbon.agents", "isLeaf": false}, map[interface{}]interface{}{"intervals": []interface{}{}, "metric_path": "carbon.aggregator", "isLeaf": false}, map[interface{}]interface{}{"intervals": []interface{}{}, "metric_path": "carbon.relays", "isLeaf": false}}}, {"graphite message3", string(graphitePickle3), []interface{}{map[interface{}]interface{}{"intervals": []interface{}{}, "metric_path": "carbon.agents", "isLeaf": false}, map[interface{}]interface{}{"intervals": []interface{}{}, "metric_path": "carbon.aggregator", "isLeaf": false}, map[interface{}]interface{}{"intervals": []interface{}{}, "metric_path": "carbon.relays", "isLeaf": false}}},
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment