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