From 28512d5d07a2caad829868ffe06199401ccdf188 Mon Sep 17 00:00:00 2001 From: Terry Zhao Date: Thu, 26 Mar 2026 12:34:09 -0700 Subject: [PATCH] fixed sequencer issue --- go.mod | 2 +- go.sum | 4 +- service/executor/sequencer/service.go | 5 +- service/executor/sequencer/walker.go | 41 +++++++++++++- service/executor/sequencer/walker_test.go | 65 +++++++++++++++++++++++ 5 files changed, 112 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 14aa0fab7..68e589e69 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/viant/parsly v0.3.3 github.com/viant/pgo v0.11.0 github.com/viant/scy v0.24.0 - github.com/viant/sqlx v0.21.0 + github.com/viant/sqlx v0.22.1-0.20260326175456-cec446e28372 github.com/viant/structql v0.5.4 github.com/viant/toolbox v0.37.0 github.com/viant/velty v0.4.0 diff --git a/go.sum b/go.sum index 403c8d56f..12881d103 100644 --- a/go.sum +++ b/go.sum @@ -1196,8 +1196,8 @@ github.com/viant/scy v0.24.0 h1:KAC3IUARkQxTNSuwBK2YhVBJMOOLN30YaLKHbbuSkMU= github.com/viant/scy v0.24.0/go.mod h1:7uNRS67X45YN+JqTLCcMEhehffVjqrejULEDln9p0Ao= github.com/viant/sqlparser v0.11.1-0.20260224194657-0470849e3588 h1:bnVgWzZzuz2pTa54e7YozHjYNFSapfU3MSklyMkO+Ag= github.com/viant/sqlparser v0.11.1-0.20260224194657-0470849e3588/go.mod h1:2QRGiGZYk2/pjhORGG1zLVQ9JO+bXFhqIVi31mkCRPg= -github.com/viant/sqlx v0.21.0 h1:Lx5KXmzfSjSvZZX5P0Ua9kFGvAmCxAjLOPe9pQA7VmY= -github.com/viant/sqlx v0.21.0/go.mod h1:woTOwNiqvt6SqkI+5nyzlixcRTTV0IvLZUTberqb8mo= +github.com/viant/sqlx v0.22.1-0.20260326175456-cec446e28372 h1:5qW+4AbQ8YA0MsyoUx3uaNgTHl52F0JBoC2vsdwKXIM= +github.com/viant/sqlx v0.22.1-0.20260326175456-cec446e28372/go.mod h1:woTOwNiqvt6SqkI+5nyzlixcRTTV0IvLZUTberqb8mo= github.com/viant/structology v0.9.0 h1:ibR/XmdQ3+/4XW3JK+pXRqugSnxJOm2bmIvbQ0hqztY= github.com/viant/structology v0.9.0/go.mod h1:AAFeViwniqua61sTKdOz/zlbLpN5vE4OVhDoiZJaMgA= github.com/viant/structql v0.5.4 h1:bMdcOpzU8UMoe5OBcyJVRxLAndvU1oj3ysvPUgBckCI= diff --git a/service/executor/sequencer/service.go b/service/executor/sequencer/service.go index 371af6388..92ffc66b1 100644 --- a/service/executor/sequencer/service.go +++ b/service/executor/sequencer/service.go @@ -32,10 +32,13 @@ func (s *Service) next(table string, any interface{}, selector string) error { if err != nil || emptyRecordCount == 0 { return err } - record, err := aWalker.Leaf(any) + record, err := aWalker.EmptyLeaf(any) if err != nil { return err } + if record == nil { + return nil + } inserter, err := insert.New(s.ctx, s.db, table) if err != nil { return err diff --git a/service/executor/sequencer/walker.go b/service/executor/sequencer/walker.go index 7d761177e..cd7b468d8 100644 --- a/service/executor/sequencer/walker.go +++ b/service/executor/sequencer/walker.go @@ -19,6 +19,11 @@ func (w *Walker) Leaf(value interface{}) (interface{}, error) { return w.leaf(w.root, value) } +// EmptyLeaf returns the first record whose leaf selector currently has a zero value. +func (w *Walker) EmptyLeaf(value interface{}) (interface{}, error) { + return w.emptyLeaf(w.root, value) +} + // Allocate allocate sequence func (w *Walker) Allocate(value interface{}, seq *Sequence) error { return w.allocate(w.root, value, seq) @@ -106,7 +111,7 @@ func (w *Walker) leaf(aNode *node, value interface{}) (interface{}, error) { return value, nil case nodeKindArray: sliceLen := aNode.xSlice.Len(ptr) - for i := 0; i < sliceLen; { + for i := 0; i < sliceLen; i++ { item := aNode.xSlice.ValuePointerAt(ptr, i) first, err := w.leaf(aNode.children, item) if err != nil { @@ -121,6 +126,40 @@ func (w *Walker) leaf(aNode *node, value interface{}) (interface{}, error) { return item, nil } +func (w *Walker) emptyLeaf(aNode *node, value interface{}) (interface{}, error) { + ptr := xunsafe.AsPointer(value) + var item interface{} + switch aNode.kind { + case nodeKindObject: + item = aNode.xField.Interface(ptr) + return w.emptyLeaf(aNode.children, item) + case nodeKindLeaf: + item = aNode.xField.Addr(ptr) + intPtr, err := int64Ptr(item) + if err != nil { + return nil, err + } + if *intPtr == 0 { + return value, nil + } + return nil, nil + case nodeKindArray: + sliceLen := aNode.xSlice.Len(ptr) + for i := 0; i < sliceLen; i++ { + item := aNode.xSlice.ValuePointerAt(ptr, i) + first, err := w.emptyLeaf(aNode.children, item) + if err != nil { + return nil, err + } + if first != nil { + return first, nil + } + } + return nil, nil + } + return item, nil +} + func int64Ptr(value interface{}) (*int64, error) { switch actual := value.(type) { case *int, *uint, uint64: diff --git a/service/executor/sequencer/walker_test.go b/service/executor/sequencer/walker_test.go index b51ee6a08..a8414136f 100644 --- a/service/executor/sequencer/walker_test.go +++ b/service/executor/sequencer/walker_test.go @@ -152,3 +152,68 @@ func TestWalker_Leaf(t *testing.T) { } } + +func TestWalker_EmptyLeaf(t *testing.T) { + type Foo struct { + ID int + Name string + } + + type Bar struct { + ID int + Foos []Foo + } + + testCases := []struct { + description string + value interface{} + selectors []string + expect interface{} + }{ + { + description: "nested selector returns first empty leaf owner", + value: []*Bar{ + { + ID: 1, + Foos: []Foo{ + {ID: 10, Name: "keep"}, + {ID: 0, Name: "allocate-me"}, + }, + }, + }, + selectors: []string{"Foos", "ID"}, + expect: &Foo{ID: 0, Name: "allocate-me"}, + }, + { + description: "slice selector skips non-empty ids", + value: []*Foo{ + {ID: 101, Name: "already-set"}, + {ID: 0, Name: "needs-id"}, + {ID: 0, Name: "also-needs-id"}, + }, + selectors: []string{"ID"}, + expect: &Foo{ID: 0, Name: "needs-id"}, + }, + { + description: "returns nil when there are no empty ids", + value: []*Foo{ + {ID: 101, Name: "already-set"}, + {ID: 102, Name: "also-set"}, + }, + selectors: []string{"ID"}, + expect: nil, + }, + } + + for _, testCase := range testCases { + aWalker, err := NewWalker(testCase.value, testCase.selectors) + if !assert.Nil(t, err, testCase.description) { + continue + } + actual, err := aWalker.EmptyLeaf(testCase.value) + if !assert.Nil(t, err, testCase.description) { + continue + } + assert.EqualValues(t, testCase.expect, actual, testCase.description) + } +}