Isolated fields type

This commit is contained in:
tidwall 2019-02-15 08:26:55 -07:00
parent 3ac8dc2ffb
commit a60dc57598
12 changed files with 375 additions and 205 deletions

View File

@ -119,39 +119,39 @@ func (c *Collection) delItem(item *item.Item) {
func (c *Collection) Set( func (c *Collection) Set(
id string, obj geojson.Object, fields []string, values []float64, id string, obj geojson.Object, fields []string, values []float64,
) ( ) (
oldObj geojson.Object, oldFields []float64, newFields []float64, oldObj geojson.Object, oldFields *Fields, newFields *Fields,
) { ) {
// create the new item // create the new item
item := item.New(id, obj) newItem := item.New(id, obj)
// add the new item to main btree and remove the old one if needed // add the new item to main btree and remove the old one if needed
oldItemV, ok := c.items.Set(item) var oldItem *item.Item
oldItemV, ok := c.items.Set(newItem)
if ok { if ok {
oldItem := oldItemV oldItem = oldItemV
oldObj = oldItem.Obj() oldObj = oldItem.Obj()
// remove old item from indexes // remove old item from indexes
c.delItem(oldItem) c.delItem(oldItem)
if len(oldItem.Fields()) > 0 { if oldItem.HasFields() {
// merge old and new fields // merge old and new fields
oldFields = oldItem.Fields() newItem.CopyOverFields(oldItem)
item.CopyOverFields(oldFields)
} }
} }
if fields == nil && len(values) > 0 { if fields == nil && len(values) > 0 {
// directly set the field values, from copy // directly set the field values, from copy
item.CopyOverFields(values) newItem.CopyOverFields(values)
} else if len(fields) > 0 { } else if len(fields) > 0 {
// add new field to new item // add new field to new item
c.setFields(item, fields, values, false) c.setFields(newItem, fields, values, false)
} }
// add new item to indexes // add new item to indexes
c.addItem(item) c.addItem(newItem)
// fmt.Printf("!!! %#v\n", oldObj) // fmt.Printf("!!! %#v\n", oldObj)
return oldObj, oldFields, item.Fields() return oldObj, itemFields(oldItem), itemFields(newItem)
} }
func (c *Collection) setFields( func (c *Collection) setFields(
@ -192,7 +192,7 @@ func (c *Collection) setField(
// Delete removes an object and returns it. // Delete removes an object and returns it.
// If the object does not exist then the 'ok' return value will be false. // If the object does not exist then the 'ok' return value will be false.
func (c *Collection) Delete(id string) ( func (c *Collection) Delete(id string) (
obj geojson.Object, fields []float64, ok bool, obj geojson.Object, fields *Fields, ok bool,
) { ) {
oldItemV, ok := c.items.Delete(id) oldItemV, ok := c.items.Delete(id)
if !ok { if !ok {
@ -202,13 +202,13 @@ func (c *Collection) Delete(id string) (
c.delItem(oldItem) c.delItem(oldItem)
return oldItem.Obj(), oldItem.Fields(), true return oldItem.Obj(), itemFields(oldItem), true
} }
// Get returns an object. // Get returns an object.
// If the object does not exist then the 'ok' return value will be false. // If the object does not exist then the 'ok' return value will be false.
func (c *Collection) Get(id string) ( func (c *Collection) Get(id string) (
obj geojson.Object, fields []float64, ok bool, obj geojson.Object, fields *Fields, ok bool,
) { ) {
itemV, ok := c.items.Get(id) itemV, ok := c.items.Get(id)
if !ok { if !ok {
@ -216,13 +216,13 @@ func (c *Collection) Get(id string) (
} }
item := itemV item := itemV
return item.Obj(), item.Fields(), true return item.Obj(), itemFields(item), true
} }
// SetField set a field value for an object and returns that object. // SetField set a field value for an object and returns that object.
// If the object does not exist then the 'ok' return value will be false. // If the object does not exist then the 'ok' return value will be false.
func (c *Collection) SetField(id, fieldName string, fieldValue float64) ( func (c *Collection) SetField(id, fieldName string, fieldValue float64) (
obj geojson.Object, fields []float64, updated bool, ok bool, obj geojson.Object, fields *Fields, updated bool, ok bool,
) { ) {
itemV, ok := c.items.Get(id) itemV, ok := c.items.Get(id)
if !ok { if !ok {
@ -230,13 +230,13 @@ func (c *Collection) SetField(id, fieldName string, fieldValue float64) (
} }
item := itemV item := itemV
updated = c.setField(item, fieldName, fieldValue, true) updated = c.setField(item, fieldName, fieldValue, true)
return item.Obj(), item.Fields(), updated, true return item.Obj(), itemFields(item), updated, true
} }
// SetFields is similar to SetField, just setting multiple fields at once // SetFields is similar to SetField, just setting multiple fields at once
func (c *Collection) SetFields( func (c *Collection) SetFields(
id string, fieldNames []string, fieldValues []float64, id string, fieldNames []string, fieldValues []float64,
) (obj geojson.Object, fields []float64, updatedCount int, ok bool) { ) (obj geojson.Object, fields *Fields, updatedCount int, ok bool) {
itemV, ok := c.items.Get(id) itemV, ok := c.items.Get(id)
if !ok { if !ok {
return nil, nil, 0, false return nil, nil, 0, false
@ -245,7 +245,7 @@ func (c *Collection) SetFields(
updatedCount = c.setFields(item, fieldNames, fieldValues, true) updatedCount = c.setFields(item, fieldNames, fieldValues, true)
return item.Obj(), item.Fields(), updatedCount, true return item.Obj(), itemFields(item), updatedCount, true
} }
// FieldMap return a maps of the field names. // FieldMap return a maps of the field names.
@ -264,7 +264,7 @@ func (c *Collection) FieldArr() []string {
// Scan iterates though the collection ids. // Scan iterates though the collection ids.
func (c *Collection) Scan(desc bool, cursor Cursor, func (c *Collection) Scan(desc bool, cursor Cursor,
iterator func(id string, obj geojson.Object, fields []float64) bool, iterator func(id string, obj geojson.Object, fields *Fields) bool,
) bool { ) bool {
var keepon = true var keepon = true
var count uint64 var count uint64
@ -281,7 +281,7 @@ func (c *Collection) Scan(desc bool, cursor Cursor,
if cursor != nil { if cursor != nil {
cursor.Step(1) cursor.Step(1)
} }
keepon = iterator(item.ID(), item.Obj(), item.Fields()) keepon = iterator(item.ID(), item.Obj(), itemFields(item))
return keepon return keepon
} }
if desc { if desc {
@ -294,7 +294,7 @@ func (c *Collection) Scan(desc bool, cursor Cursor,
// ScanRange iterates though the collection starting with specified id. // ScanRange iterates though the collection starting with specified id.
func (c *Collection) ScanRange(start, end string, desc bool, cursor Cursor, func (c *Collection) ScanRange(start, end string, desc bool, cursor Cursor,
iterator func(id string, obj geojson.Object, fields []float64) bool, iterator func(id string, obj geojson.Object, fields *Fields) bool,
) bool { ) bool {
var keepon = true var keepon = true
var count uint64 var count uint64
@ -320,7 +320,7 @@ func (c *Collection) ScanRange(start, end string, desc bool, cursor Cursor,
return false return false
} }
} }
keepon = iterator(item.ID(), item.Obj(), item.Fields()) keepon = iterator(item.ID(), item.Obj(), itemFields(item))
return keepon return keepon
} }
@ -334,7 +334,7 @@ func (c *Collection) ScanRange(start, end string, desc bool, cursor Cursor,
// SearchValues iterates though the collection values. // SearchValues iterates though the collection values.
func (c *Collection) SearchValues(desc bool, cursor Cursor, func (c *Collection) SearchValues(desc bool, cursor Cursor,
iterator func(id string, obj geojson.Object, fields []float64) bool, iterator func(id string, obj geojson.Object, fields *Fields) bool,
) bool { ) bool {
var keepon = true var keepon = true
var count uint64 var count uint64
@ -352,7 +352,7 @@ func (c *Collection) SearchValues(desc bool, cursor Cursor,
cursor.Step(1) cursor.Step(1)
} }
iitm := v.(*item.Item) iitm := v.(*item.Item)
keepon = iterator(iitm.ID(), iitm.Obj(), iitm.Fields()) keepon = iterator(iitm.ID(), iitm.Obj(), itemFields(iitm))
return keepon return keepon
} }
if desc { if desc {
@ -366,7 +366,7 @@ func (c *Collection) SearchValues(desc bool, cursor Cursor,
// SearchValuesRange iterates though the collection values. // SearchValuesRange iterates though the collection values.
func (c *Collection) SearchValuesRange(start, end string, desc bool, func (c *Collection) SearchValuesRange(start, end string, desc bool,
cursor Cursor, cursor Cursor,
iterator func(id string, obj geojson.Object, fields []float64) bool, iterator func(id string, obj geojson.Object, fields *Fields) bool,
) bool { ) bool {
var keepon = true var keepon = true
var count uint64 var count uint64
@ -384,7 +384,7 @@ func (c *Collection) SearchValuesRange(start, end string, desc bool,
cursor.Step(1) cursor.Step(1)
} }
iitm := v.(*item.Item) iitm := v.(*item.Item)
keepon = iterator(iitm.ID(), iitm.Obj(), iitm.Fields()) keepon = iterator(iitm.ID(), iitm.Obj(), itemFields(iitm))
return keepon return keepon
} }
if desc { if desc {
@ -402,7 +402,7 @@ func (c *Collection) SearchValuesRange(start, end string, desc bool,
// ScanGreaterOrEqual iterates though the collection starting with specified id. // ScanGreaterOrEqual iterates though the collection starting with specified id.
func (c *Collection) ScanGreaterOrEqual(id string, desc bool, func (c *Collection) ScanGreaterOrEqual(id string, desc bool,
cursor Cursor, cursor Cursor,
iterator func(id string, obj geojson.Object, fields []float64) bool, iterator func(id string, obj geojson.Object, fields *Fields) bool,
) bool { ) bool {
var keepon = true var keepon = true
var count uint64 var count uint64
@ -419,7 +419,7 @@ func (c *Collection) ScanGreaterOrEqual(id string, desc bool,
if cursor != nil { if cursor != nil {
cursor.Step(1) cursor.Step(1)
} }
keepon = iterator(item.ID(), item.Obj(), item.Fields()) keepon = iterator(item.ID(), item.Obj(), itemFields(item))
return keepon return keepon
} }
if desc { if desc {
@ -432,7 +432,7 @@ func (c *Collection) ScanGreaterOrEqual(id string, desc bool,
func (c *Collection) geoSearch( func (c *Collection) geoSearch(
rect geometry.Rect, rect geometry.Rect,
iter func(id string, obj geojson.Object, fields []float64) bool, iter func(id string, obj geojson.Object, fields *Fields) bool,
) bool { ) bool {
alive := true alive := true
c.index.Search( c.index.Search(
@ -440,7 +440,7 @@ func (c *Collection) geoSearch(
[]float64{rect.Max.X, rect.Max.Y}, []float64{rect.Max.X, rect.Max.Y},
func(_, _ []float64, itemv *item.Item) bool { func(_, _ []float64, itemv *item.Item) bool {
item := itemv item := itemv
alive = iter(item.ID(), item.Obj(), item.Fields()) alive = iter(item.ID(), item.Obj(), itemFields(item))
return alive return alive
}, },
) )
@ -449,12 +449,12 @@ func (c *Collection) geoSearch(
func (c *Collection) geoSparse( func (c *Collection) geoSparse(
obj geojson.Object, sparse uint8, obj geojson.Object, sparse uint8,
iter func(id string, obj geojson.Object, fields []float64) (match, ok bool), iter func(id string, obj geojson.Object, fields *Fields) (match, ok bool),
) bool { ) bool {
matches := make(map[string]bool) matches := make(map[string]bool)
alive := true alive := true
c.geoSparseInner(obj.Rect(), sparse, c.geoSparseInner(obj.Rect(), sparse,
func(id string, o geojson.Object, fields []float64) ( func(id string, o geojson.Object, fields *Fields) (
match, ok bool, match, ok bool,
) { ) {
ok = true ok = true
@ -471,7 +471,7 @@ func (c *Collection) geoSparse(
} }
func (c *Collection) geoSparseInner( func (c *Collection) geoSparseInner(
rect geometry.Rect, sparse uint8, rect geometry.Rect, sparse uint8,
iter func(id string, obj geojson.Object, fields []float64) (match, ok bool), iter func(id string, obj geojson.Object, fields *Fields) (match, ok bool),
) bool { ) bool {
if sparse > 0 { if sparse > 0 {
w := rect.Max.X - rect.Min.X w := rect.Max.X - rect.Min.X
@ -503,7 +503,7 @@ func (c *Collection) geoSparseInner(
} }
alive := true alive := true
c.geoSearch(rect, c.geoSearch(rect,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
match, ok := iter(id, obj, fields) match, ok := iter(id, obj, fields)
if !ok { if !ok {
alive = false alive = false
@ -521,7 +521,7 @@ func (c *Collection) Within(
obj geojson.Object, obj geojson.Object,
sparse uint8, sparse uint8,
cursor Cursor, cursor Cursor,
iter func(id string, obj geojson.Object, fields []float64) bool, iter func(id string, obj geojson.Object, fields *Fields) bool,
) bool { ) bool {
var count uint64 var count uint64
var offset uint64 var offset uint64
@ -531,7 +531,7 @@ func (c *Collection) Within(
} }
if sparse > 0 { if sparse > 0 {
return c.geoSparse(obj, sparse, return c.geoSparse(obj, sparse,
func(id string, o geojson.Object, fields []float64) ( func(id string, o geojson.Object, fields *Fields) (
match, ok bool, match, ok bool,
) { ) {
count++ count++
@ -549,7 +549,7 @@ func (c *Collection) Within(
) )
} }
return c.geoSearch(obj.Rect(), return c.geoSearch(obj.Rect(),
func(id string, o geojson.Object, fields []float64) bool { func(id string, o geojson.Object, fields *Fields) bool {
count++ count++
if count <= offset { if count <= offset {
return true return true
@ -571,7 +571,7 @@ func (c *Collection) Intersects(
obj geojson.Object, obj geojson.Object,
sparse uint8, sparse uint8,
cursor Cursor, cursor Cursor,
iter func(id string, obj geojson.Object, fields []float64) bool, iter func(id string, obj geojson.Object, fields *Fields) bool,
) bool { ) bool {
var count uint64 var count uint64
var offset uint64 var offset uint64
@ -581,7 +581,7 @@ func (c *Collection) Intersects(
} }
if sparse > 0 { if sparse > 0 {
return c.geoSparse(obj, sparse, return c.geoSparse(obj, sparse,
func(id string, o geojson.Object, fields []float64) ( func(id string, o geojson.Object, fields *Fields) (
match, ok bool, match, ok bool,
) { ) {
count++ count++
@ -599,7 +599,7 @@ func (c *Collection) Intersects(
) )
} }
return c.geoSearch(obj.Rect(), return c.geoSearch(obj.Rect(),
func(id string, o geojson.Object, fields []float64) bool { func(id string, o geojson.Object, fields *Fields) bool {
count++ count++
if count <= offset { if count <= offset {
return true return true
@ -619,7 +619,7 @@ func (c *Collection) Intersects(
func (c *Collection) Nearby( func (c *Collection) Nearby(
target geojson.Object, target geojson.Object,
cursor Cursor, cursor Cursor,
iter func(id string, obj geojson.Object, fields []float64) bool, iter func(id string, obj geojson.Object, fields *Fields) bool,
) bool { ) bool {
// First look to see if there's at least one candidate in the circle's // First look to see if there's at least one candidate in the circle's
// outer rectangle. This is a fast-fail operation. // outer rectangle. This is a fast-fail operation.
@ -665,7 +665,7 @@ func (c *Collection) Nearby(
cursor.Step(1) cursor.Step(1)
} }
item := itemv item := itemv
alive = iter(item.ID(), item.Obj(), item.Fields()) alive = iter(item.ID(), item.Obj(), itemFields(item))
return alive return alive
}, },
) )

View File

@ -104,7 +104,7 @@ func TestCollectionNewCollection(t *testing.T) {
Min: geometry.Point{X: -180, Y: -90}, Min: geometry.Point{X: -180, Y: -90},
Max: geometry.Point{X: 180, Y: 90}, Max: geometry.Point{X: 180, Y: 90},
} }
c.geoSearch(bbox, func(id string, obj geojson.Object, field []float64) bool { c.geoSearch(bbox, func(id string, obj geojson.Object, field *Fields) bool {
count++ count++
return true return true
}) })
@ -124,8 +124,8 @@ func TestCollectionSet(t *testing.T) {
str1 := String("hello") str1 := String("hello")
oldObject, oldFields, newFields := c.Set("str", str1, nil, nil) oldObject, oldFields, newFields := c.Set("str", str1, nil, nil)
expect(t, oldObject == nil) expect(t, oldObject == nil)
expect(t, len(oldFields) == 0) expect(t, fieldLen(oldFields) == 0)
expect(t, len(newFields) == 0) expect(t, fieldLen(newFields) == 0)
}) })
t.Run("UpdateString", func(t *testing.T) { t.Run("UpdateString", func(t *testing.T) {
c := New() c := New()
@ -133,20 +133,20 @@ func TestCollectionSet(t *testing.T) {
str2 := String("world") str2 := String("world")
oldObject, oldFields, newFields := c.Set("str", str1, nil, nil) oldObject, oldFields, newFields := c.Set("str", str1, nil, nil)
expect(t, oldObject == nil) expect(t, oldObject == nil)
expect(t, len(oldFields) == 0) expect(t, fieldLen(oldFields) == 0)
expect(t, len(newFields) == 0) expect(t, fieldLen(newFields) == 0)
oldObject, oldFields, newFields = c.Set("str", str2, nil, nil) oldObject, oldFields, newFields = c.Set("str", str2, nil, nil)
expect(t, oldObject == str1) expect(t, oldObject == str1)
expect(t, len(oldFields) == 0) expect(t, fieldLen(oldFields) == 0)
expect(t, len(newFields) == 0) expect(t, fieldLen(newFields) == 0)
}) })
t.Run("AddPoint", func(t *testing.T) { t.Run("AddPoint", func(t *testing.T) {
c := New() c := New()
point1 := PO(-112.1, 33.1) point1 := PO(-112.1, 33.1)
oldObject, oldFields, newFields := c.Set("point", point1, nil, nil) oldObject, oldFields, newFields := c.Set("point", point1, nil, nil)
expect(t, oldObject == nil) expect(t, oldObject == nil)
expect(t, len(oldFields) == 0) expect(t, fieldLen(oldFields) == 0)
expect(t, len(newFields) == 0) expect(t, fieldLen(newFields) == 0)
}) })
t.Run("UpdatePoint", func(t *testing.T) { t.Run("UpdatePoint", func(t *testing.T) {
c := New() c := New()
@ -154,12 +154,12 @@ func TestCollectionSet(t *testing.T) {
point2 := PO(-112.2, 33.2) point2 := PO(-112.2, 33.2)
oldObject, oldFields, newFields := c.Set("point", point1, nil, nil) oldObject, oldFields, newFields := c.Set("point", point1, nil, nil)
expect(t, oldObject == nil) expect(t, oldObject == nil)
expect(t, len(oldFields) == 0) expect(t, fieldLen(oldFields) == 0)
expect(t, len(newFields) == 0) expect(t, fieldLen(newFields) == 0)
oldObject, oldFields, newFields = c.Set("point", point2, nil, nil) oldObject, oldFields, newFields = c.Set("point", point2, nil, nil)
expect(t, oldObject == point1) expect(t, oldObject == point1)
expect(t, len(oldFields) == 0) expect(t, fieldLen(oldFields) == 0)
expect(t, len(newFields) == 0) expect(t, fieldLen(newFields) == 0)
}) })
t.Run("Fields", func(t *testing.T) { t.Run("Fields", func(t *testing.T) {
c := New() c := New()
@ -170,23 +170,23 @@ func TestCollectionSet(t *testing.T) {
fValues := []float64{1, 2, 3} fValues := []float64{1, 2, 3}
oldObj, oldFlds, newFlds := c.Set("str", str1, fNames, fValues) oldObj, oldFlds, newFlds := c.Set("str", str1, fNames, fValues)
expect(t, oldObj == nil) expect(t, oldObj == nil)
expect(t, len(oldFlds) == 0) expect(t, fieldLen(oldFlds) == 0)
expect(t, reflect.DeepEqual(newFlds, fValues)) expect(t, fieldIterEquals(newFlds, fValues))
} }
{ {
fNames := []string{"d", "e", "f"} fNames := []string{"d", "e", "f"}
fValues := []float64{4, 5, 6} fValues := []float64{4, 5, 6}
oldObj, oldFlds, newFlds := c.Set("str", str2, fNames, fValues) oldObj, oldFlds, newFlds := c.Set("str", str2, fNames, fValues)
expect(t, oldObj == str1) expect(t, oldObj == str1)
expect(t, reflect.DeepEqual(oldFlds, []float64{1, 2, 3})) expect(t, fieldIterEquals(oldFlds, []float64{1, 2, 3}))
expect(t, reflect.DeepEqual(newFlds, []float64{1, 2, 3, 4, 5, 6})) expect(t, fieldIterEquals(newFlds, []float64{1, 2, 3, 4, 5, 6}))
} }
{ {
fValues := []float64{7, 8, 9, 10, 11, 12} fValues := []float64{7, 8, 9, 10, 11, 12}
oldObj, oldFlds, newFlds := c.Set("str", str1, nil, fValues) oldObj, oldFlds, newFlds := c.Set("str", str1, nil, fValues)
expect(t, oldObj == str2) expect(t, oldObj == str2)
expect(t, reflect.DeepEqual(oldFlds, []float64{1, 2, 3, 4, 5, 6})) expect(t, fieldIterEquals(oldFlds, []float64{1, 2, 3, 4, 5, 6}))
expect(t, reflect.DeepEqual(newFlds, []float64{7, 8, 9, 10, 11, 12})) expect(t, fieldIterEquals(newFlds, []float64{7, 8, 9, 10, 11, 12}))
} }
}) })
t.Run("Delete", func(t *testing.T) { t.Run("Delete", func(t *testing.T) {
@ -204,7 +204,7 @@ func TestCollectionSet(t *testing.T) {
Max: geometry.Point{X: 1, Y: 2}}) Max: geometry.Point{X: 1, Y: 2}})
var v geojson.Object var v geojson.Object
var ok bool var ok bool
var flds []float64 var flds *Fields
var updated bool var updated bool
var updateCount int var updateCount int
@ -226,24 +226,24 @@ func TestCollectionSet(t *testing.T) {
v, flds, updated, ok = c.SetField("3", "hello", 123) v, flds, updated, ok = c.SetField("3", "hello", 123)
expect(t, ok) expect(t, ok)
expect(t, reflect.DeepEqual(flds, []float64{123})) expect(t, fieldIterEquals(flds, []float64{123}))
expect(t, updated) expect(t, updated)
expect(t, c.FieldMap()["hello"] == 0) expect(t, c.FieldMap()["hello"] == 0)
v, flds, updated, ok = c.SetField("3", "hello", 1234) v, flds, updated, ok = c.SetField("3", "hello", 1234)
expect(t, ok) expect(t, ok)
expect(t, reflect.DeepEqual(flds, []float64{1234})) expect(t, fieldIterEquals(flds, []float64{1234}))
expect(t, updated) expect(t, updated)
v, flds, updated, ok = c.SetField("3", "hello", 1234) v, flds, updated, ok = c.SetField("3", "hello", 1234)
expect(t, ok) expect(t, ok)
expect(t, reflect.DeepEqual(flds, []float64{1234})) expect(t, fieldIterEquals(flds, []float64{1234}))
expect(t, !updated) expect(t, !updated)
v, flds, updateCount, ok = c.SetFields("3", v, flds, updateCount, ok = c.SetFields("3",
[]string{"planet", "world"}, []float64{55, 66}) []string{"planet", "world"}, []float64{55, 66})
expect(t, ok) expect(t, ok)
expect(t, reflect.DeepEqual(flds, []float64{1234, 55, 66})) expect(t, fieldIterEquals(flds, []float64{1234, 55, 66}))
expect(t, updateCount == 2) expect(t, updateCount == 2)
expect(t, c.FieldMap()["hello"] == 0) expect(t, c.FieldMap()["hello"] == 0)
expect(t, c.FieldMap()["planet"] == 1) expect(t, c.FieldMap()["planet"] == 1)
@ -277,6 +277,29 @@ func TestCollectionSet(t *testing.T) {
}) })
} }
func fieldLen(fields *Fields) int {
var idx int
fields.ForEach(-1, func(value float64) bool {
idx++
return true
})
return idx
}
func fieldIterEquals(fields *Fields, values []float64) bool {
ok := true
var idx int
fields.ForEach(len(values), func(value float64) bool {
if value != values[idx] {
ok = false
return false
}
idx++
return true
})
return ok
}
func TestCollectionScan(t *testing.T) { func TestCollectionScan(t *testing.T) {
N := 256 N := 256
c := New() c := New()
@ -286,22 +309,22 @@ func TestCollectionScan(t *testing.T) {
} }
var n int var n int
var prevID string var prevID string
c.Scan(false, nil, func(id string, obj geojson.Object, fields []float64) bool { c.Scan(false, nil, func(id string, obj geojson.Object, fields *Fields) bool {
if n > 0 { if n > 0 {
expect(t, id > prevID) expect(t, id > prevID)
} }
expect(t, id == fmt.Sprintf("%04d", int(fields[0]))) expect(t, id == fmt.Sprintf("%04d", int(fields.Get(0))))
n++ n++
prevID = id prevID = id
return true return true
}) })
expect(t, n == c.Count()) expect(t, n == c.Count())
n = 0 n = 0
c.Scan(true, nil, func(id string, obj geojson.Object, fields []float64) bool { c.Scan(true, nil, func(id string, obj geojson.Object, fields *Fields) bool {
if n > 0 { if n > 0 {
expect(t, id < prevID) expect(t, id < prevID)
} }
expect(t, id == fmt.Sprintf("%04d", int(fields[0]))) expect(t, id == fmt.Sprintf("%04d", int(fields.Get(0))))
n++ n++
prevID = id prevID = id
return true return true
@ -310,11 +333,11 @@ func TestCollectionScan(t *testing.T) {
n = 0 n = 0
c.ScanRange("0060", "0070", false, nil, c.ScanRange("0060", "0070", false, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
if n > 0 { if n > 0 {
expect(t, id > prevID) expect(t, id > prevID)
} }
expect(t, id == fmt.Sprintf("%04d", int(fields[0]))) expect(t, id == fmt.Sprintf("%04d", int(fields.Get(0))))
n++ n++
prevID = id prevID = id
return true return true
@ -323,11 +346,11 @@ func TestCollectionScan(t *testing.T) {
n = 0 n = 0
c.ScanRange("0070", "0060", true, nil, c.ScanRange("0070", "0060", true, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
if n > 0 { if n > 0 {
expect(t, id < prevID) expect(t, id < prevID)
} }
expect(t, id == fmt.Sprintf("%04d", int(fields[0]))) expect(t, id == fmt.Sprintf("%04d", int(fields.Get(0))))
n++ n++
prevID = id prevID = id
return true return true
@ -336,11 +359,11 @@ func TestCollectionScan(t *testing.T) {
n = 0 n = 0
c.ScanGreaterOrEqual("0070", true, nil, c.ScanGreaterOrEqual("0070", true, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
if n > 0 { if n > 0 {
expect(t, id < prevID) expect(t, id < prevID)
} }
expect(t, id == fmt.Sprintf("%04d", int(fields[0]))) expect(t, id == fmt.Sprintf("%04d", int(fields.Get(0))))
n++ n++
prevID = id prevID = id
return true return true
@ -349,11 +372,11 @@ func TestCollectionScan(t *testing.T) {
n = 0 n = 0
c.ScanGreaterOrEqual("0070", false, nil, c.ScanGreaterOrEqual("0070", false, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
if n > 0 { if n > 0 {
expect(t, id > prevID) expect(t, id > prevID)
} }
expect(t, id == fmt.Sprintf("%04d", int(fields[0]))) expect(t, id == fmt.Sprintf("%04d", int(fields.Get(0))))
n++ n++
prevID = id prevID = id
return true return true
@ -373,22 +396,22 @@ func TestCollectionSearch(t *testing.T) {
} }
var n int var n int
var prevValue string var prevValue string
c.SearchValues(false, nil, func(id string, obj geojson.Object, fields []float64) bool { c.SearchValues(false, nil, func(id string, obj geojson.Object, fields *Fields) bool {
if n > 0 { if n > 0 {
expect(t, obj.String() > prevValue) expect(t, obj.String() > prevValue)
} }
expect(t, id == fmt.Sprintf("%04d", int(fields[1]))) expect(t, id == fmt.Sprintf("%04d", int(fields.Get(1))))
n++ n++
prevValue = obj.String() prevValue = obj.String()
return true return true
}) })
expect(t, n == c.Count()) expect(t, n == c.Count())
n = 0 n = 0
c.SearchValues(true, nil, func(id string, obj geojson.Object, fields []float64) bool { c.SearchValues(true, nil, func(id string, obj geojson.Object, fields *Fields) bool {
if n > 0 { if n > 0 {
expect(t, obj.String() < prevValue) expect(t, obj.String() < prevValue)
} }
expect(t, id == fmt.Sprintf("%04d", int(fields[1]))) expect(t, id == fmt.Sprintf("%04d", int(fields.Get(1))))
n++ n++
prevValue = obj.String() prevValue = obj.String()
return true return true
@ -397,11 +420,11 @@ func TestCollectionSearch(t *testing.T) {
n = 0 n = 0
c.SearchValuesRange("0060", "0070", false, nil, c.SearchValuesRange("0060", "0070", false, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
if n > 0 { if n > 0 {
expect(t, obj.String() > prevValue) expect(t, obj.String() > prevValue)
} }
expect(t, id == fmt.Sprintf("%04d", int(fields[1]))) expect(t, id == fmt.Sprintf("%04d", int(fields.Get(1))))
n++ n++
prevValue = obj.String() prevValue = obj.String()
return true return true
@ -410,11 +433,11 @@ func TestCollectionSearch(t *testing.T) {
n = 0 n = 0
c.SearchValuesRange("0070", "0060", true, nil, c.SearchValuesRange("0070", "0060", true, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
if n > 0 { if n > 0 {
expect(t, obj.String() < prevValue) expect(t, obj.String() < prevValue)
} }
expect(t, id == fmt.Sprintf("%04d", int(fields[1]))) expect(t, id == fmt.Sprintf("%04d", int(fields.Get(1))))
n++ n++
prevValue = obj.String() prevValue = obj.String()
return true return true
@ -493,7 +516,7 @@ func TestSpatialSearch(t *testing.T) {
n = 0 n = 0
c.Within(q1, 0, nil, c.Within(q1, 0, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
n++ n++
return true return true
}, },
@ -502,7 +525,7 @@ func TestSpatialSearch(t *testing.T) {
n = 0 n = 0
c.Within(q2, 0, nil, c.Within(q2, 0, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
n++ n++
return true return true
}, },
@ -511,7 +534,7 @@ func TestSpatialSearch(t *testing.T) {
n = 0 n = 0
c.Within(q3, 0, nil, c.Within(q3, 0, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
n++ n++
return true return true
}, },
@ -520,7 +543,7 @@ func TestSpatialSearch(t *testing.T) {
n = 0 n = 0
c.Intersects(q1, 0, nil, c.Intersects(q1, 0, nil,
func(_ string, _ geojson.Object, _ []float64) bool { func(_ string, _ geojson.Object, _ *Fields) bool {
n++ n++
return true return true
}, },
@ -529,7 +552,7 @@ func TestSpatialSearch(t *testing.T) {
n = 0 n = 0
c.Intersects(q2, 0, nil, c.Intersects(q2, 0, nil,
func(_ string, _ geojson.Object, _ []float64) bool { func(_ string, _ geojson.Object, _ *Fields) bool {
n++ n++
return true return true
}, },
@ -538,7 +561,7 @@ func TestSpatialSearch(t *testing.T) {
n = 0 n = 0
c.Intersects(q3, 0, nil, c.Intersects(q3, 0, nil,
func(_ string, _ geojson.Object, _ []float64) bool { func(_ string, _ geojson.Object, _ *Fields) bool {
n++ n++
return true return true
}, },
@ -547,7 +570,7 @@ func TestSpatialSearch(t *testing.T) {
n = 0 n = 0
c.Intersects(q3, 0, nil, c.Intersects(q3, 0, nil,
func(_ string, _ geojson.Object, _ []float64) bool { func(_ string, _ geojson.Object, _ *Fields) bool {
n++ n++
return n <= 1 return n <= 1
}, },
@ -559,7 +582,7 @@ func TestSpatialSearch(t *testing.T) {
r2, p1, p4, r1, p3, r3, p2, r2, p1, p4, r1, p3, r3, p2,
} }
c.Nearby(q4, nil, c.Nearby(q4, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
items = append(items, obj) items = append(items, obj)
return true return true
}, },
@ -585,7 +608,7 @@ func TestCollectionSparse(t *testing.T) {
var n int var n int
n = 0 n = 0
c.Within(rect, 1, nil, c.Within(rect, 1, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
n++ n++
return true return true
}, },
@ -594,7 +617,7 @@ func TestCollectionSparse(t *testing.T) {
n = 0 n = 0
c.Within(rect, 2, nil, c.Within(rect, 2, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
n++ n++
return true return true
}, },
@ -603,7 +626,7 @@ func TestCollectionSparse(t *testing.T) {
n = 0 n = 0
c.Within(rect, 3, nil, c.Within(rect, 3, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
n++ n++
return true return true
}, },
@ -612,7 +635,7 @@ func TestCollectionSparse(t *testing.T) {
n = 0 n = 0
c.Within(rect, 3, nil, c.Within(rect, 3, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *Fields) bool {
n++ n++
return n <= 30 return n <= 30
}, },
@ -621,7 +644,7 @@ func TestCollectionSparse(t *testing.T) {
n = 0 n = 0
c.Intersects(rect, 3, nil, c.Intersects(rect, 3, nil,
func(id string, _ geojson.Object, _ []float64) bool { func(id string, _ geojson.Object, _ *Fields) bool {
n++ n++
return true return true
}, },
@ -630,7 +653,7 @@ func TestCollectionSparse(t *testing.T) {
n = 0 n = 0
c.Intersects(rect, 3, nil, c.Intersects(rect, 3, nil,
func(id string, _ geojson.Object, _ []float64) bool { func(id string, _ geojson.Object, _ *Fields) bool {
n++ n++
return n <= 30 return n <= 30
}, },
@ -683,7 +706,7 @@ func TestManyCollections(t *testing.T) {
Min: geometry.Point{X: -180, Y: 30}, Min: geometry.Point{X: -180, Y: 30},
Max: geometry.Point{X: 34, Y: 100}, Max: geometry.Point{X: 34, Y: 100},
} }
col.geoSearch(bbox, func(id string, obj geojson.Object, fields []float64) bool { col.geoSearch(bbox, func(id string, obj geojson.Object, fields *Fields) bool {
//println(id) //println(id)
return true return true
}) })

View File

@ -0,0 +1,40 @@
package collection
import "github.com/tidwall/tile38/internal/collection/item"
// // FieldIter ...
// type FieldIter interface {
// ForEachField(count int, iter func(value float64) bool)
// GetField(index int) float64
// HasFields() bool
// }
// Fields ...
type Fields struct {
item *item.Item
}
// ForEach iterates over each field. The count param is the number of
// iterations. When count is less than zero, then all fields are returns.
func (fields *Fields) ForEach(count int, iter func(value float64) bool) {
if fields == nil || fields.item == nil {
return
}
fields.item.ForEachField(count, iter)
}
// Get returns the value for a field at index. If there is no field at index,
// then zero is returned.
func (fields *Fields) Get(index int) float64 {
if fields == nil || fields.item == nil {
return 0
}
return fields.item.GetField(index)
}
func itemFields(item *item.Item) *Fields {
if item == nil || !item.HasFields() {
return nil
}
return &Fields{item: item}
}

View File

@ -39,7 +39,7 @@ func (item *Item) ID() string {
} }
// Fields returns the field values // Fields returns the field values
func (item *Item) Fields() []float64 { func (item *Item) fields() []float64 {
return *(*[]float64)((unsafe.Pointer)(&reflect.SliceHeader{ return *(*[]float64)((unsafe.Pointer)(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(item.data)), Data: uintptr(unsafe.Pointer(item.data)),
Len: int(item.fieldsLen) / 8, Len: int(item.fieldsLen) / 8,
@ -104,8 +104,15 @@ func (item *Item) Less(other btree.Item, ctx interface{}) bool {
return item.ID() < other.(*Item).ID() return item.ID() < other.(*Item).ID()
} }
// CopyOverFields overwriting previous fields // CopyOverFields overwriting previous fields. Accepts an *Item or []float64
func (item *Item) CopyOverFields(values []float64) { func (item *Item) CopyOverFields(from interface{}) {
var values []float64
switch from := from.(type) {
case *Item:
values = from.fields()
case []float64:
values = from
}
fieldBytes := floatsToBytes(values) fieldBytes := floatsToBytes(values)
oldData := item.dataBytes() oldData := item.dataBytes()
newData := make([]byte, len(fieldBytes)+int(item.idLen)) newData := make([]byte, len(fieldBytes)+int(item.idLen))
@ -153,15 +160,6 @@ func (item *Item) SetField(index int, value float64) (updated bool) {
return true return true
} }
// GetField returns the value for a field at index.
func (item *Item) GetField(index int) float64 {
numFields := int(item.fieldsLen / 8)
if index < numFields {
return getFieldAt(item.data, index)
}
return 0
}
func (item *Item) dataBytes() []byte { func (item *Item) dataBytes() []byte {
return *(*[]byte)((unsafe.Pointer)(&reflect.SliceHeader{ return *(*[]byte)((unsafe.Pointer)(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(item.data)), Data: uintptr(unsafe.Pointer(item.data)),
@ -177,3 +175,44 @@ func floatsToBytes(f []float64) []byte {
Cap: len(f) * 8, Cap: len(f) * 8,
})) }))
} }
// ForEachField iterates over each field. The count param is the number of
// iterations. When count is less than zero, then all fields are returns.
func (item *Item) ForEachField(count int, iter func(value float64) bool) {
if item == nil {
return
}
fields := item.fields()
var n int
if count < 0 {
n = len(fields)
} else {
n = count
}
for i := 0; i < n; i++ {
var field float64
if i < len(fields) {
field = fields[i]
}
if !iter(field) {
return
}
}
}
// GetField returns the value for a field at index.
func (item *Item) GetField(index int) float64 {
if item == nil {
return 0
}
numFields := int(item.fieldsLen / 8)
if index < numFields {
return getFieldAt(item.data, index)
}
return 0
}
// HasFields returns true when item has fields
func (item *Item) HasFields() bool {
return item != nil && item.fieldsLen > 0
}

View File

@ -19,7 +19,12 @@ func testRandItem(t *testing.T) {
for i := range values { for i := range values {
values[i] = rand.Float64() values[i] = rand.Float64()
} }
item := New(key, geojson.NewPoint(geometry.Point{X: 1, Y: 2})) var item *Item
if rand.Int()%2 == 0 {
item = New(key, geojson.NewSimplePoint(geometry.Point{X: 1, Y: 2}))
} else {
item = New(key, geojson.NewPoint(geometry.Point{X: 1, Y: 2}))
}
if item.ID() != key { if item.ID() != key {
t.Fatalf("expected '%v', got '%v'", key, item.ID()) t.Fatalf("expected '%v', got '%v'", key, item.ID())
} }
@ -38,7 +43,7 @@ func testRandItem(t *testing.T) {
t.Fatalf("expected '%v', got '%v'", values[i], item.GetField(i)) t.Fatalf("expected '%v', got '%v'", values[i], item.GetField(i))
} }
} }
fields := item.Fields() fields := item.fields()
for i := 0; i < len(fields); i++ { for i := 0; i < len(fields); i++ {
for _, j := range setValues { for _, j := range setValues {
if i == j { if i == j {
@ -65,6 +70,42 @@ func testRandItem(t *testing.T) {
t.Fatal("expected false") t.Fatal("expected false")
} }
} }
var fvalues []float64
item.ForEachField(len(values), func(value float64) bool {
fvalues = append(fvalues, value)
return true
})
if !reflect.DeepEqual(values, fvalues) {
t.Fatalf("expected '%v', got '%v'", values, fvalues)
}
fvalues = nil
item.ForEachField(len(values), func(value float64) bool {
if len(fvalues) == 1 {
return false
}
fvalues = append(fvalues, value)
return true
})
if len(values) > 0 && len(fvalues) != 1 {
t.Fatalf("expected '%v', got '%v'", 1, len(fvalues))
}
fvalues = nil
item.ForEachField(-1, func(value float64) bool {
fvalues = append(fvalues, value)
return true
})
if !reflect.DeepEqual(values, fvalues) {
t.Fatalf("expected '%v', got '%v'", 1, len(fvalues))
}
// should not fail, must allow nil receiver
(*Item)(nil).ForEachField(1, nil)
if (*Item)(nil).GetField(1) != 0 {
t.Fatalf("expected '%v', got '%v'", 0, (*Item)(nil).GetField(1))
}
if item.ID() != key { if item.ID() != key {
t.Fatalf("expected '%v', got '%v'", key, item.ID()) t.Fatalf("expected '%v', got '%v'", key, item.ID())
} }
@ -79,8 +120,22 @@ func testRandItem(t *testing.T) {
if points != 1 { if points != 1 {
t.Fatalf("expected '%v', got '%v'", 1, points) t.Fatalf("expected '%v', got '%v'", 1, points)
} }
if !reflect.DeepEqual(item.Fields(), values) { if !reflect.DeepEqual(item.fields(), values) {
t.Fatalf("expected '%v', got '%v'", values, item.Fields()) t.Fatalf("expected '%v', got '%v'", values, item.fields())
}
item.CopyOverFields(item)
weight, points = item.WeightAndPoints()
if weight != len(values)*8+len(key)+points*16 {
t.Fatalf("expected '%v', got '%v'", len(values)*8+len(key)+points*16, weight)
}
if points != 1 {
t.Fatalf("expected '%v', got '%v'", 1, points)
}
if !reflect.DeepEqual(item.fields(), values) {
t.Fatalf("expected '%v', got '%v'", values, item.fields())
}
if !item.HasFields() {
t.Fatal("expected true")
} }
item.CopyOverFields(nil) item.CopyOverFields(nil)
@ -91,12 +146,16 @@ func testRandItem(t *testing.T) {
if points != 1 { if points != 1 {
t.Fatalf("expected '%v', got '%v'", 1, points) t.Fatalf("expected '%v', got '%v'", 1, points)
} }
if len(item.Fields()) != 0 { if len(item.fields()) != 0 {
t.Fatalf("expected '%#v', got '%#v'", 0, len(item.Fields())) t.Fatalf("expected '%#v', got '%#v'", 0, len(item.fields()))
} }
if item.ID() != key { if item.ID() != key {
t.Fatalf("expected '%v', got '%v'", key, item.ID()) t.Fatalf("expected '%v', got '%v'", key, item.ID())
} }
if item.HasFields() {
t.Fatal("expected false")
}
} }
func TestItem(t *testing.T) { func TestItem(t *testing.T) {

View File

@ -96,8 +96,9 @@ func (server *Server) aofshrink() {
var exm = server.expires[keys[0]] // the expiration map var exm = server.expires[keys[0]] // the expiration map
var now = time.Now() // used for expiration var now = time.Now() // used for expiration
var count = 0 // the object count var count = 0 // the object count
col.ScanGreaterOrEqual(nextid, false, nil, col.ScanGreaterOrEqual(nextid, false, nil,
func(id string, obj geojson.Object, fields []float64) bool { func(id string, obj geojson.Object, fields *collection.Fields) bool {
if count == maxids { if count == maxids {
// we reached the max number of ids for one batch // we reached the max number of ids for one batch
nextid = id nextid = id
@ -110,13 +111,16 @@ func (server *Server) aofshrink() {
values = append(values, "set") values = append(values, "set")
values = append(values, keys[0]) values = append(values, keys[0])
values = append(values, id) values = append(values, id)
for i, fvalue := range fields { var fidx int
fields.ForEach(len(fnames), func(fvalue float64) bool {
if fvalue != 0 { if fvalue != 0 {
values = append(values, "field") values = append(values, "field")
values = append(values, fnames[i]) values = append(values, fnames[fidx])
values = append(values, strconv.FormatFloat(fvalue, 'f', -1, 64)) values = append(values, strconv.FormatFloat(fvalue, 'f', -1, 64))
} }
} fidx++
return true
})
if exm != nil { if exm != nil {
at, ok := exm[id] at, ok := exm[id]
if ok { if ok {

View File

@ -34,16 +34,14 @@ func (a byField) Swap(i, j int) {
a[i], a[j] = a[j], a[i] a[i], a[j] = a[j], a[i]
} }
func orderFields(fmap map[string]int, fields []float64) []fvt { func orderFields(fmap map[string]int, fields *collection.Fields) []fvt {
var fv fvt var fv fvt
fvs := make([]fvt, 0, len(fmap)) fvs := make([]fvt, 0, len(fmap))
for field, idx := range fmap { for field, idx := range fmap {
if idx < len(fields) { fv.field = field
fv.field = field fv.value = fields.Get(idx)
fv.value = fields[idx] if fv.value != 0 {
if fv.value != 0 { fvs = append(fvs, fv)
fvs = append(fvs, fv)
}
} }
} }
sort.Sort(byField(fvs)) sort.Sort(byField(fvs))
@ -352,7 +350,7 @@ func (server *Server) cmdPdel(msg *Message) (res resp.Value, d commandDetails, e
return return
} }
now := time.Now() now := time.Now()
iter := func(id string, o geojson.Object, fields []float64) bool { iter := func(id string, o geojson.Object, fields *collection.Fields) bool {
if match, _ := glob.Match(d.pattern, id); match { if match, _ := glob.Match(d.pattern, id); match {
d.children = append(d.children, &commandDetails{ d.children = append(d.children, &commandDetails{
command: "del", command: "del",

View File

@ -9,6 +9,7 @@ import (
"github.com/tidwall/geojson/geo" "github.com/tidwall/geojson/geo"
"github.com/tidwall/geojson/geometry" "github.com/tidwall/geojson/geometry"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/tidwall/tile38/internal/collection"
"github.com/tidwall/tile38/internal/glob" "github.com/tidwall/tile38/internal/glob"
) )
@ -284,7 +285,7 @@ func extendRoamMessage(
} }
pattern := match.id + fence.roam.scan pattern := match.id + fence.roam.scan
iterator := func( iterator := func(
oid string, o geojson.Object, fields []float64, oid string, o geojson.Object, fields *collection.Fields,
) bool { ) bool {
if oid == match.id { if oid == match.id {
return true return true
@ -371,7 +372,7 @@ func fenceMatchRoam(
Max: geometry.Point{X: maxLon, Y: maxLat}, Max: geometry.Point{X: maxLon, Y: maxLat},
} }
col.Intersects(geojson.NewRect(rect), 0, nil, func( col.Intersects(geojson.NewRect(rect), 0, nil, func(
id string, obj2 geojson.Object, fields []float64, id string, obj2 geojson.Object, fields *collection.Fields,
) bool { ) bool {
if c.hasExpired(fence.roam.key, id) { if c.hasExpired(fence.roam.key, id) {
return true return true

View File

@ -7,6 +7,7 @@ import (
"github.com/tidwall/geojson" "github.com/tidwall/geojson"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/collection"
"github.com/tidwall/tile38/internal/glob" "github.com/tidwall/tile38/internal/glob"
) )
@ -67,7 +68,7 @@ func (c *Server) cmdScan(msg *Message) (res resp.Value, err error) {
g := glob.Parse(sw.globPattern, s.desc) g := glob.Parse(sw.globPattern, s.desc)
if g.Limits[0] == "" && g.Limits[1] == "" { if g.Limits[0] == "" && g.Limits[1] == "" {
sw.col.Scan(s.desc, sw, sw.col.Scan(s.desc, sw,
func(id string, o geojson.Object, fields []float64) bool { func(id string, o geojson.Object, fields *collection.Fields) bool {
return sw.writeObject(ScanWriterParams{ return sw.writeObject(ScanWriterParams{
id: id, id: id,
o: o, o: o,
@ -77,7 +78,7 @@ func (c *Server) cmdScan(msg *Message) (res resp.Value, err error) {
) )
} else { } else {
sw.col.ScanRange(g.Limits[0], g.Limits[1], s.desc, sw, sw.col.ScanRange(g.Limits[0], g.Limits[1], s.desc, sw,
func(id string, o geojson.Object, fields []float64) bool { func(id string, o geojson.Object, fields *collection.Fields) bool {
return sw.writeObject(ScanWriterParams{ return sw.writeObject(ScanWriterParams{
id: id, id: id,
o: o, o: o,

View File

@ -64,7 +64,7 @@ type scanWriter struct {
type ScanWriterParams struct { type ScanWriterParams struct {
id string id string
o geojson.Object o geojson.Object
fields []float64 fields *collection.Fields
distance float64 distance float64
noLock bool noLock bool
ignoreGlobMatch bool ignoreGlobMatch bool
@ -194,7 +194,9 @@ func (sw *scanWriter) writeFoot() {
} }
} }
func (sw *scanWriter) fieldMatch(fields []float64, o geojson.Object) (fvals []float64, match bool) { func (sw *scanWriter) fieldMatch(
fields *collection.Fields, o geojson.Object,
) (fvals []float64, match bool) {
var z float64 var z float64
var gotz bool var gotz bool
fvals = sw.fvals fvals = sw.fvals
@ -212,9 +214,7 @@ func (sw *scanWriter) fieldMatch(fields []float64, o geojson.Object) (fvals []fl
var value float64 var value float64
idx, ok := sw.fmap[where.field] idx, ok := sw.fmap[where.field]
if ok { if ok {
if len(fields) > idx { value = fields.Get(idx)
value = fields[idx]
}
} }
if !where.match(value) { if !where.match(value) {
return return
@ -224,9 +224,7 @@ func (sw *scanWriter) fieldMatch(fields []float64, o geojson.Object) (fvals []fl
var value float64 var value float64
idx, ok := sw.fmap[wherein.field] idx, ok := sw.fmap[wherein.field]
if ok { if ok {
if len(fields) > idx { value = fields.Get(idx)
value = fields[idx]
}
} }
if !wherein.match(value) { if !wherein.match(value) {
return return
@ -235,11 +233,7 @@ func (sw *scanWriter) fieldMatch(fields []float64, o geojson.Object) (fvals []fl
for _, whereval := range sw.whereevals { for _, whereval := range sw.whereevals {
fieldsWithNames := make(map[string]float64) fieldsWithNames := make(map[string]float64)
for field, idx := range sw.fmap { for field, idx := range sw.fmap {
if idx < len(fields) { fieldsWithNames[field] = fields.Get(idx)
fieldsWithNames[field] = fields[idx]
} else {
fieldsWithNames[field] = 0
}
} }
if !whereval.match(fieldsWithNames) { if !whereval.match(fieldsWithNames) {
return return
@ -247,11 +241,7 @@ func (sw *scanWriter) fieldMatch(fields []float64, o geojson.Object) (fvals []fl
} }
} else { } else {
for idx := range sw.farr { for idx := range sw.farr {
var value float64 sw.fvals[idx] = fields.Get(idx)
if len(fields) > idx {
value = fields[idx]
}
sw.fvals[idx] = value
} }
for _, where := range sw.wheres { for _, where := range sw.wheres {
if where.field == "z" { if where.field == "z" {
@ -285,11 +275,7 @@ func (sw *scanWriter) fieldMatch(fields []float64, o geojson.Object) (fvals []fl
for _, whereval := range sw.whereevals { for _, whereval := range sw.whereevals {
fieldsWithNames := make(map[string]float64) fieldsWithNames := make(map[string]float64)
for field, idx := range sw.fmap { for field, idx := range sw.fmap {
if idx < len(fields) { fieldsWithNames[field] = fields.Get(idx)
fieldsWithNames[field] = fields[idx]
} else {
fieldsWithNames[field] = 0
}
} }
if !whereval.match(fieldsWithNames) { if !whereval.match(fieldsWithNames) {
return return
@ -333,7 +319,10 @@ func (sw *scanWriter) Step(n uint64) {
// ok is whether the object passes the test and should be written // ok is whether the object passes the test and should be written
// keepGoing is whether there could be more objects to test // keepGoing is whether there could be more objects to test
func (sw *scanWriter) testObject(id string, o geojson.Object, fields []float64, ignoreGlobMatch bool) ( func (sw *scanWriter) testObject(
id string, o geojson.Object,
fields *collection.Fields, ignoreGlobMatch bool,
) (
ok, keepGoing bool, fieldVals []float64) { ok, keepGoing bool, fieldVals []float64) {
if !ignoreGlobMatch { if !ignoreGlobMatch {
match, kg := sw.globMatch(id, o) match, kg := sw.globMatch(id, o)
@ -384,14 +373,13 @@ func (sw *scanWriter) writeObject(opts ScanWriterParams) bool {
jsfields = `,"fields":{` jsfields = `,"fields":{`
var i int var i int
for field, idx := range sw.fmap { for field, idx := range sw.fmap {
if len(opts.fields) > idx { fvalue := opts.fields.Get(idx)
if opts.fields[idx] != 0 { if fvalue != 0 {
if i > 0 { if i > 0 {
jsfields += `,` jsfields += `,`
}
jsfields += jsonString(field) + ":" + strconv.FormatFloat(opts.fields[idx], 'f', -1, 64)
i++
} }
jsfields += jsonString(field) + ":" + strconv.FormatFloat(fvalue, 'f', -1, 64)
i++
} }
} }
jsfields += `}` jsfields += `}`

View File

@ -14,6 +14,7 @@ import (
"github.com/tidwall/geojson/geometry" "github.com/tidwall/geojson/geometry"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/bing" "github.com/tidwall/tile38/internal/bing"
"github.com/tidwall/tile38/internal/collection"
"github.com/tidwall/tile38/internal/glob" "github.com/tidwall/tile38/internal/glob"
) )
@ -370,7 +371,12 @@ func (server *Server) cmdNearby(msg *Message) (res resp.Value, err error) {
} }
sw.writeHead() sw.writeHead()
if sw.col != nil { if sw.col != nil {
iter := func(id string, o geojson.Object, fields []float64, dist float64) bool { iter := func(
id string,
o geojson.Object,
fields *collection.Fields,
dist float64,
) bool {
meters := 0.0 meters := 0.0
if s.distance { if s.distance {
meters = geo.DistanceFromHaversine(dist) meters = geo.DistanceFromHaversine(dist)
@ -398,35 +404,38 @@ func (server *Server) cmdNearby(msg *Message) (res resp.Value, err error) {
type iterItem struct { type iterItem struct {
id string id string
o geojson.Object o geojson.Object
fields []float64 fields *collection.Fields
dist float64 dist float64
} }
func (server *Server) nearestNeighbors( func (server *Server) nearestNeighbors(
s *liveFenceSwitches, sw *scanWriter, target *geojson.Circle, s *liveFenceSwitches, sw *scanWriter, target *geojson.Circle,
iter func(id string, o geojson.Object, fields []float64, dist float64, iter func(
id string, o geojson.Object, fields *collection.Fields, dist float64,
) bool) { ) bool) {
maxDist := target.Haversine() maxDist := target.Haversine()
limit := int(sw.limit) limit := int(sw.limit)
var items []iterItem var items []iterItem
sw.col.Nearby(target, sw, func(id string, o geojson.Object, fields []float64) bool { sw.col.Nearby(
if server.hasExpired(s.key, id) { target, sw,
return true func(id string, o geojson.Object, fields *collection.Fields) bool {
} if server.hasExpired(s.key, id) {
ok, keepGoing, _ := sw.testObject(id, o, fields, true) return true
if !ok { }
return true ok, keepGoing, _ := sw.testObject(id, o, fields, true)
} if !ok {
dist := target.HaversineTo(o.Center()) return true
if maxDist > 0 && dist > maxDist { }
return false dist := target.HaversineTo(o.Center())
} if maxDist > 0 && dist > maxDist {
items = append(items, iterItem{id: id, o: o, fields: fields, dist: dist}) return false
if !keepGoing { }
return false items = append(items, iterItem{id: id, o: o, fields: fields, dist: dist})
} if !keepGoing {
return len(items) < limit return false
}) }
return len(items) < limit
})
sort.Slice(items, func(i, j int) bool { sort.Slice(items, func(i, j int) bool {
return items[i].dist < items[j].dist return items[i].dist < items[j].dist
}) })
@ -481,7 +490,7 @@ func (server *Server) cmdWithinOrIntersects(cmd string, msg *Message) (res resp.
if sw.col != nil { if sw.col != nil {
if cmd == "within" { if cmd == "within" {
sw.col.Within(s.obj, s.sparse, sw, func( sw.col.Within(s.obj, s.sparse, sw, func(
id string, o geojson.Object, fields []float64, id string, o geojson.Object, fields *collection.Fields,
) bool { ) bool {
if server.hasExpired(s.key, id) { if server.hasExpired(s.key, id) {
return true return true
@ -497,7 +506,7 @@ func (server *Server) cmdWithinOrIntersects(cmd string, msg *Message) (res resp.
sw.col.Intersects(s.obj, s.sparse, sw, func( sw.col.Intersects(s.obj, s.sparse, sw, func(
id string, id string,
o geojson.Object, o geojson.Object,
fields []float64, fields *collection.Fields,
) bool { ) bool {
if server.hasExpired(s.key, id) { if server.hasExpired(s.key, id) {
return true return true
@ -579,7 +588,11 @@ func (server *Server) cmdSearch(msg *Message) (res resp.Value, err error) {
g := glob.Parse(sw.globPattern, s.desc) g := glob.Parse(sw.globPattern, s.desc)
if g.Limits[0] == "" && g.Limits[1] == "" { if g.Limits[0] == "" && g.Limits[1] == "" {
sw.col.SearchValues(s.desc, sw, sw.col.SearchValues(s.desc, sw,
func(id string, o geojson.Object, fields []float64) bool { func(
id string,
o geojson.Object,
fields *collection.Fields,
) bool {
return sw.writeObject(ScanWriterParams{ return sw.writeObject(ScanWriterParams{
id: id, id: id,
o: o, o: o,
@ -593,7 +606,11 @@ func (server *Server) cmdSearch(msg *Message) (res resp.Value, err error) {
// globSingle is only for ID matches, not values. // globSingle is only for ID matches, not values.
sw.globSingle = false sw.globSingle = false
sw.col.SearchValuesRange(g.Limits[0], g.Limits[1], s.desc, sw, sw.col.SearchValuesRange(g.Limits[0], g.Limits[1], s.desc, sw,
func(id string, o geojson.Object, fields []float64) bool { func(
id string,
o geojson.Object,
fields *collection.Fields,
) bool {
return sw.writeObject(ScanWriterParams{ return sw.writeObject(ScanWriterParams{
id: id, id: id,
o: o, o: o,

View File

@ -47,19 +47,19 @@ const (
// commandDetails is detailed information about a mutable command. It's used // commandDetails is detailed information about a mutable command. It's used
// for geofence formulas. // for geofence formulas.
type commandDetails struct { type commandDetails struct {
command string // client command, like "SET" or "DEL" command string // client command, like "SET" or "DEL"
key, id string // collection key and object id of object key, id string // collection key and object id of object
newKey string // new key, for RENAME command newKey string // new key, for RENAME command
fmap map[string]int // map of field names to value indexes obj geojson.Object // new object
obj geojson.Object // new object fmap map[string]int // map of field names to value indexes
fields []float64 // array of field values fields *collection.Fields // array of field values
oldObj geojson.Object // previous object, if any oldObj geojson.Object // previous object, if any
oldFields []float64 // previous object field values oldFields *collection.Fields // previous object field values
updated bool // object was updated updated bool // object was updated
timestamp time.Time // timestamp when the update occured timestamp time.Time // timestamp when the update occured
parent bool // when true, only children are forwarded parent bool // when true, only children are forwarded
pattern string // PDEL key pattern pattern string // PDEL key pattern
children []*commandDetails // for multi actions such as "PDEL" children []*commandDetails // for multi actions such as "PDEL"
} }
// Server is a tile38 controller // Server is a tile38 controller