Skip to content

Commit

Permalink
Add RecordID to types.Document (#3495)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlekSi authored Oct 5, 2023
1 parent f9f5e9a commit 0a56f5b
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .github/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ repository:
# https://docs.github.com/en/rest/issues/labels
labels:
# area
- name: area/admin
color: "#2931BF"
description: Issues about administration commands
- name: area/aggregations
color: "#5319E7"
description: Issues about aggregation pipelines
Expand Down
2 changes: 1 addition & 1 deletion internal/handlers/sjson/sjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ func unmarshalSingleValue(data json.RawMessage, sch *elem) (any, error) {
return fromSJSON(res), nil
}

// Marshal encodes the given document and set its schema in the field $s.
// Marshal encodes given document fields and set its schema in the field $s.
// Use it when you need to encode a document with schema, for example, when you want to store it in a database.
func Marshal(d *types.Document) ([]byte, error) {
if d == nil {
Expand Down
27 changes: 23 additions & 4 deletions internal/types/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@ type document interface {

// Document represents BSON document: an ordered collection of fields
// (key/value pairs where key is a string and value is any BSON value).
//
// Data documents (that are stored in the backend) have a special RecordID property
// that is not a field and can't be accessed by most methods.
// It use used to locate the document in the backend.
type Document struct {
fields []field
frozen bool
fields []field
frozen bool
recordID Timestamp
}

// field represents a field in the document.
// RecordID is not a field.
//
// The order of field is like that to reduce a pressure on gc a bit, and make vet/fieldalignment linter happy.
type field struct {
Expand Down Expand Up @@ -122,8 +128,20 @@ func NewDocument(pairs ...any) (*Document, error) {

func (*Document) compositeType() {}

// Freeze prevents document from further modifications.
// Any methods that would modify the document will panic.
// RecordID returns the document's RecordID (that is 0 by default).
func (d *Document) RecordID() Timestamp {
return d.recordID
}

// SetRecordID sets the document's RecordID.
func (d *Document) SetRecordID(recordID Timestamp) {
d.recordID = recordID
}

// Freeze prevents document from further field modifications.
// Any methods that would modify document fields will panic.
//
// RecordID modification is not prevented.
//
// It is safe to call Freeze multiple times.
func (d *Document) Freeze() {
Expand All @@ -140,6 +158,7 @@ func (d *Document) checkFrozen() {
}

// DeepCopy returns an unfrozen deep copy of this Document.
// RecordID is copied too.
func (d *Document) DeepCopy() *Document {
if d == nil {
panic("types.Document.DeepCopy: nil document")
Expand Down
2 changes: 1 addition & 1 deletion internal/types/object_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const ObjectIDLen = 12

// NewObjectID returns a new ObjectID.
func NewObjectID() ObjectID {
return newObjectIDTime(time.Now())
return newObjectIDTime(time.Now()) // https://github.com/FerretDB/FerretDB/issues/3486
}

// newObjectIDTime returns a new ObjectID with given time.
Expand Down
10 changes: 10 additions & 0 deletions internal/types/timestamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ func NewTimestamp(t time.Time, c uint32) Timestamp {
return Timestamp((uint64(t.Unix()) << 32) | uint64(c))
}

// NewTimestampSigned returns the timestamp for the given signed value.
func NewTimestampSigned(i int64) Timestamp {
return Timestamp(i)
}

// NextTimestamp returns the next timestamp for the given time value.
func NextTimestamp(t time.Time) Timestamp {
// Technically, that should be a counter within a second, not a process-wide,
Expand All @@ -44,3 +49,8 @@ func (ts Timestamp) Time() time.Time {
sec := int64(ts >> 32)
return time.Unix(sec, 0).UTC()
}

// Signed returns the timestamp as a signed value.
func (ts Timestamp) Signed() int64 {
return int64(ts)
}
19 changes: 19 additions & 0 deletions internal/types/timestamp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,22 @@ func TestNextTimestamp(t *testing.T) {
assert.Equal(t, d, NextTimestamp(d).Time())
})
}

func TestNewTimestampSigned(t *testing.T) {
// one second before Y2K38
now := time.Date(2038, time.January, 19, 3, 14, 6, 0, time.UTC)

ts1 := NextTimestamp(now)
ts2 := NextTimestamp(now.Add(time.Second))
ts3 := NextTimestamp(now.Add(2 * time.Second))

assert.Less(t, ts1, ts2)
assert.Less(t, ts2, ts3)

assert.Less(t, ts1.Signed(), ts2.Signed())
assert.Greater(t, ts2.Signed(), ts3.Signed(), "expected Epochalypse")

assert.Equal(t, ts1, NewTimestampSigned(ts1.Signed()))
assert.Equal(t, ts2, NewTimestampSigned(ts2.Signed()))
assert.Equal(t, ts3, NewTimestampSigned(ts3.Signed()))
}
3 changes: 2 additions & 1 deletion internal/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ func deepCopy(value any) any {
}

return &Document{
fields: fields,
fields: fields,
recordID: value.recordID,
}

case *Array:
Expand Down

0 comments on commit 0a56f5b

Please sign in to comment.