github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/internal/table/scanner/result.go (about) 1 package scanner 2 3 import ( 4 "context" 5 "errors" 6 "io" 7 "sync/atomic" 8 9 "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" 10 "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_TableStats" 11 12 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 13 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync" 14 "github.com/ydb-platform/ydb-go-sdk/v3/table/result" 15 "github.com/ydb-platform/ydb-go-sdk/v3/table/stats" 16 ) 17 18 var errAlreadyClosed = xerrors.Wrap(errors.New("result closed early")) 19 20 type baseResult struct { 21 valueScanner 22 23 nextResultSetCounter atomic.Uint64 24 statsMtx xsync.RWMutex 25 stats *Ydb_TableStats.QueryStats 26 27 closed atomic.Bool 28 } 29 30 type streamResult struct { 31 baseResult 32 33 recv func(ctx context.Context) (*Ydb.ResultSet, *Ydb_TableStats.QueryStats, error) 34 close func(error) error 35 } 36 37 // Err returns error caused Scanner to be broken. 38 func (r *streamResult) Err() error { 39 err := r.valueScanner.Err() 40 if err != nil { 41 return xerrors.WithStackTrace(err) 42 } 43 44 return nil 45 } 46 47 type unaryResult struct { 48 baseResult 49 50 sets []*Ydb.ResultSet 51 nextSet int 52 } 53 54 // Err returns error caused Scanner to be broken. 55 func (r *unaryResult) Err() error { 56 err := r.valueScanner.Err() 57 if err != nil { 58 return xerrors.WithStackTrace(err) 59 } 60 61 return nil 62 } 63 64 // Close closes the result, preventing further iteration. 65 func (r *unaryResult) Close() error { 66 if r.closed.CompareAndSwap(false, true) { 67 return nil 68 } 69 70 return xerrors.WithStackTrace(errAlreadyClosed) 71 } 72 73 func (r *unaryResult) ResultSetCount() int { 74 return len(r.sets) 75 } 76 77 func (r *baseResult) isClosed() bool { 78 return r.closed.Load() 79 } 80 81 type resultWithError interface { 82 SetErr(err error) 83 } 84 85 type UnaryResult interface { 86 result.Result 87 resultWithError 88 } 89 90 type StreamResult interface { 91 result.StreamResult 92 resultWithError 93 } 94 95 type option func(r *baseResult) 96 97 func WithIgnoreTruncated(ignoreTruncated bool) option { 98 return func(r *baseResult) { 99 r.valueScanner.ignoreTruncated = ignoreTruncated 100 } 101 } 102 103 func WithMarkTruncatedAsRetryable() option { 104 return func(r *baseResult) { 105 r.valueScanner.markTruncatedAsRetryable = true 106 } 107 } 108 109 func NewStream( 110 ctx context.Context, 111 recv func(ctx context.Context) (*Ydb.ResultSet, *Ydb_TableStats.QueryStats, error), 112 onClose func(error) error, 113 opts ...option, 114 ) (StreamResult, error) { 115 r := &streamResult{ 116 recv: recv, 117 close: onClose, 118 } 119 for _, o := range opts { 120 if o != nil { 121 o(&r.baseResult) 122 } 123 } 124 if err := r.nextResultSetErr(ctx); err != nil { 125 return nil, xerrors.WithStackTrace(err) 126 } 127 128 return r, nil 129 } 130 131 func NewUnary(sets []*Ydb.ResultSet, stats *Ydb_TableStats.QueryStats, opts ...option) UnaryResult { 132 r := &unaryResult{ 133 baseResult: baseResult{ 134 stats: stats, 135 }, 136 sets: sets, 137 } 138 for _, o := range opts { 139 if o != nil { 140 o(&r.baseResult) 141 } 142 } 143 144 return r 145 } 146 147 func (r *baseResult) Reset(set *Ydb.ResultSet, columnNames ...string) { 148 r.reset(set) 149 if set != nil { 150 r.setColumnIndexes(columnNames) 151 } 152 } 153 154 func (r *unaryResult) NextResultSetErr(ctx context.Context, columns ...string) (err error) { 155 if r.isClosed() { 156 return xerrors.WithStackTrace(errAlreadyClosed) 157 } 158 if !r.HasNextResultSet() { 159 return io.EOF 160 } 161 r.Reset(r.sets[r.nextSet], columns...) 162 r.nextSet++ 163 164 return ctx.Err() 165 } 166 167 func (r *unaryResult) NextResultSet(ctx context.Context, columns ...string) bool { 168 return r.NextResultSetErr(ctx, columns...) == nil 169 } 170 171 func (r *streamResult) nextResultSetErr(ctx context.Context, columns ...string) (err error) { 172 // skipping second recv because first call of recv is from New Stream(), second call is from user 173 if r.nextResultSetCounter.Add(1) == 2 { 174 r.setColumnIndexes(columns) 175 176 return ctx.Err() 177 } 178 s, stats, err := r.recv(ctx) 179 if err != nil { 180 r.Reset(nil) 181 if xerrors.Is(err, io.EOF) { 182 return err 183 } 184 185 return r.errorf(1, "streamResult.NextResultSetErr(): %w", err) 186 } 187 r.Reset(s, columns...) 188 if stats != nil { 189 r.statsMtx.WithLock(func() { 190 r.stats = stats 191 }) 192 } 193 194 return ctx.Err() 195 } 196 197 func (r *streamResult) NextResultSetErr(ctx context.Context, columns ...string) (err error) { 198 if r.isClosed() { 199 return xerrors.WithStackTrace(errAlreadyClosed) 200 } 201 if err = r.Err(); err != nil { 202 return xerrors.WithStackTrace(err) 203 } 204 if err := r.nextResultSetErr(ctx, columns...); err != nil { 205 if xerrors.Is(err, io.EOF) { 206 return io.EOF 207 } 208 209 return xerrors.WithStackTrace(err) 210 } 211 212 return nil 213 } 214 215 func (r *streamResult) NextResultSet(ctx context.Context, columns ...string) bool { 216 return r.NextResultSetErr(ctx, columns...) == nil 217 } 218 219 // CurrentResultSet get current result set 220 func (r *baseResult) CurrentResultSet() result.Set { 221 return r 222 } 223 224 // Stats returns query execution queryStats. 225 func (r *baseResult) Stats() stats.QueryStats { 226 var s queryStats 227 r.statsMtx.WithRLock(func() { 228 s.stats = r.stats 229 }) 230 231 if s.stats == nil { 232 return nil 233 } 234 235 return &s 236 } 237 238 // Close closes the result, preventing further iteration. 239 func (r *streamResult) Close() (err error) { 240 if r.closed.CompareAndSwap(false, true) { 241 return r.close(r.Err()) 242 } 243 244 return xerrors.WithStackTrace(errAlreadyClosed) 245 } 246 247 func (r *baseResult) inactive() bool { 248 return r.isClosed() || r.Err() != nil 249 } 250 251 // HasNextResultSet reports whether result set may be advanced. 252 // It may be useful to call HasNextResultSet() instead of NextResultSet() to look ahead 253 // without advancing the result set. 254 // Note that it does not work with sets from stream. 255 func (r *streamResult) HasNextResultSet() bool { 256 return !r.inactive() 257 } 258 259 // HasNextResultSet reports whether result set may be advanced. 260 // It may be useful to call HasNextResultSet() instead of NextResultSet() to look ahead 261 // without advancing the result set. 262 // Note that it does not work with sets from stream. 263 func (r *unaryResult) HasNextResultSet() bool { 264 if r.inactive() || r.nextSet >= len(r.sets) { 265 return false 266 } 267 268 return true 269 }