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.
 
 
 

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