You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

223 lines
6.7 KiB

  1. // Copyright 2015 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package bigquery
  15. import (
  16. "context"
  17. "fmt"
  18. "reflect"
  19. bq "google.golang.org/api/bigquery/v2"
  20. "google.golang.org/api/iterator"
  21. )
  22. // Construct a RowIterator.
  23. // If pf is nil, there are no rows in the result set.
  24. func newRowIterator(ctx context.Context, t *Table, pf pageFetcher) *RowIterator {
  25. it := &RowIterator{
  26. ctx: ctx,
  27. table: t,
  28. pf: pf,
  29. }
  30. if pf != nil {
  31. it.pageInfo, it.nextFunc = iterator.NewPageInfo(
  32. it.fetch,
  33. func() int { return len(it.rows) },
  34. func() interface{} { r := it.rows; it.rows = nil; return r })
  35. }
  36. return it
  37. }
  38. // A RowIterator provides access to the result of a BigQuery lookup.
  39. type RowIterator struct {
  40. ctx context.Context
  41. table *Table
  42. pf pageFetcher
  43. pageInfo *iterator.PageInfo
  44. nextFunc func() error
  45. // StartIndex can be set before the first call to Next. If PageInfo().Token
  46. // is also set, StartIndex is ignored.
  47. StartIndex uint64
  48. // The schema of the table. Available after the first call to Next.
  49. Schema Schema
  50. // The total number of rows in the result. Available after the first call to Next.
  51. // May be zero just after rows were inserted.
  52. TotalRows uint64
  53. rows [][]Value
  54. structLoader structLoader // used to populate a pointer to a struct
  55. }
  56. // Next loads the next row into dst. Its return value is iterator.Done if there
  57. // are no more results. Once Next returns iterator.Done, all subsequent calls
  58. // will return iterator.Done.
  59. //
  60. // dst may implement ValueLoader, or may be a *[]Value, *map[string]Value, or struct pointer.
  61. //
  62. // If dst is a *[]Value, it will be set to new []Value whose i'th element
  63. // will be populated with the i'th column of the row.
  64. //
  65. // If dst is a *map[string]Value, a new map will be created if dst is nil. Then
  66. // for each schema column name, the map key of that name will be set to the column's
  67. // value. STRUCT types (RECORD types or nested schemas) become nested maps.
  68. //
  69. // If dst is pointer to a struct, each column in the schema will be matched
  70. // with an exported field of the struct that has the same name, ignoring case.
  71. // Unmatched schema columns and struct fields will be ignored.
  72. //
  73. // Each BigQuery column type corresponds to one or more Go types; a matching struct
  74. // field must be of the correct type. The correspondences are:
  75. //
  76. // STRING string
  77. // BOOL bool
  78. // INTEGER int, int8, int16, int32, int64, uint8, uint16, uint32
  79. // FLOAT float32, float64
  80. // BYTES []byte
  81. // TIMESTAMP time.Time
  82. // DATE civil.Date
  83. // TIME civil.Time
  84. // DATETIME civil.DateTime
  85. //
  86. // A repeated field corresponds to a slice or array of the element type. A STRUCT
  87. // type (RECORD or nested schema) corresponds to a nested struct or struct pointer.
  88. // All calls to Next on the same iterator must use the same struct type.
  89. //
  90. // It is an error to attempt to read a BigQuery NULL value into a struct field,
  91. // unless the field is of type []byte or is one of the special Null types: NullInt64,
  92. // NullFloat64, NullBool, NullString, NullTimestamp, NullDate, NullTime or
  93. // NullDateTime. You can also use a *[]Value or *map[string]Value to read from a
  94. // table with NULLs.
  95. func (it *RowIterator) Next(dst interface{}) error {
  96. if it.pf == nil { // There are no rows in the result set.
  97. return iterator.Done
  98. }
  99. var vl ValueLoader
  100. switch dst := dst.(type) {
  101. case ValueLoader:
  102. vl = dst
  103. case *[]Value:
  104. vl = (*valueList)(dst)
  105. case *map[string]Value:
  106. vl = (*valueMap)(dst)
  107. default:
  108. if !isStructPtr(dst) {
  109. return fmt.Errorf("bigquery: cannot convert %T to ValueLoader (need pointer to []Value, map[string]Value, or struct)", dst)
  110. }
  111. }
  112. if err := it.nextFunc(); err != nil {
  113. return err
  114. }
  115. row := it.rows[0]
  116. it.rows = it.rows[1:]
  117. if vl == nil {
  118. // This can only happen if dst is a pointer to a struct. We couldn't
  119. // set vl above because we need the schema.
  120. if err := it.structLoader.set(dst, it.Schema); err != nil {
  121. return err
  122. }
  123. vl = &it.structLoader
  124. }
  125. return vl.Load(row, it.Schema)
  126. }
  127. func isStructPtr(x interface{}) bool {
  128. t := reflect.TypeOf(x)
  129. return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
  130. }
  131. // PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
  132. func (it *RowIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
  133. func (it *RowIterator) fetch(pageSize int, pageToken string) (string, error) {
  134. res, err := it.pf(it.ctx, it.table, it.Schema, it.StartIndex, int64(pageSize), pageToken)
  135. if err != nil {
  136. return "", err
  137. }
  138. it.rows = append(it.rows, res.rows...)
  139. it.Schema = res.schema
  140. it.TotalRows = res.totalRows
  141. return res.pageToken, nil
  142. }
  143. // A pageFetcher returns a page of rows from a destination table.
  144. type pageFetcher func(ctx context.Context, _ *Table, _ Schema, startIndex uint64, pageSize int64, pageToken string) (*fetchPageResult, error)
  145. type fetchPageResult struct {
  146. pageToken string
  147. rows [][]Value
  148. totalRows uint64
  149. schema Schema
  150. }
  151. // fetchPage gets a page of rows from t.
  152. func fetchPage(ctx context.Context, t *Table, schema Schema, startIndex uint64, pageSize int64, pageToken string) (*fetchPageResult, error) {
  153. // Fetch the table schema in the background, if necessary.
  154. errc := make(chan error, 1)
  155. if schema != nil {
  156. errc <- nil
  157. } else {
  158. go func() {
  159. var bqt *bq.Table
  160. err := runWithRetry(ctx, func() (err error) {
  161. bqt, err = t.c.bqs.Tables.Get(t.ProjectID, t.DatasetID, t.TableID).
  162. Fields("schema").
  163. Context(ctx).
  164. Do()
  165. return err
  166. })
  167. if err == nil && bqt.Schema != nil {
  168. schema = bqToSchema(bqt.Schema)
  169. }
  170. errc <- err
  171. }()
  172. }
  173. call := t.c.bqs.Tabledata.List(t.ProjectID, t.DatasetID, t.TableID)
  174. setClientHeader(call.Header())
  175. if pageToken != "" {
  176. call.PageToken(pageToken)
  177. } else {
  178. call.StartIndex(startIndex)
  179. }
  180. if pageSize > 0 {
  181. call.MaxResults(pageSize)
  182. }
  183. var res *bq.TableDataList
  184. err := runWithRetry(ctx, func() (err error) {
  185. res, err = call.Context(ctx).Do()
  186. return err
  187. })
  188. if err != nil {
  189. return nil, err
  190. }
  191. err = <-errc
  192. if err != nil {
  193. return nil, err
  194. }
  195. rows, err := convertRows(res.Rows, schema)
  196. if err != nil {
  197. return nil, err
  198. }
  199. return &fetchPageResult{
  200. pageToken: res.PageToken,
  201. rows: rows,
  202. totalRows: uint64(res.TotalRows),
  203. schema: schema,
  204. }, nil
  205. }