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.
 
 
 

358 lines
10 KiB

  1. // Copyright 2016 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. "encoding/base64"
  17. "errors"
  18. "fmt"
  19. "math/big"
  20. "reflect"
  21. "regexp"
  22. "time"
  23. "cloud.google.com/go/civil"
  24. "cloud.google.com/go/internal/fields"
  25. bq "google.golang.org/api/bigquery/v2"
  26. )
  27. var (
  28. // See https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#timestamp-type.
  29. timestampFormat = "2006-01-02 15:04:05.999999-07:00"
  30. // See https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#schema.fields.name
  31. validFieldName = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]{0,127}$")
  32. )
  33. const nullableTagOption = "nullable"
  34. func bqTagParser(t reflect.StructTag) (name string, keep bool, other interface{}, err error) {
  35. name, keep, opts, err := fields.ParseStandardTag("bigquery", t)
  36. if err != nil {
  37. return "", false, nil, err
  38. }
  39. if name != "" && !validFieldName.MatchString(name) {
  40. return "", false, nil, errInvalidFieldName
  41. }
  42. for _, opt := range opts {
  43. if opt != nullableTagOption {
  44. return "", false, nil, fmt.Errorf(
  45. "bigquery: invalid tag option %q. The only valid option is %q",
  46. opt, nullableTagOption)
  47. }
  48. }
  49. return name, keep, opts, nil
  50. }
  51. var fieldCache = fields.NewCache(bqTagParser, nil, nil)
  52. var (
  53. int64ParamType = &bq.QueryParameterType{Type: "INT64"}
  54. float64ParamType = &bq.QueryParameterType{Type: "FLOAT64"}
  55. boolParamType = &bq.QueryParameterType{Type: "BOOL"}
  56. stringParamType = &bq.QueryParameterType{Type: "STRING"}
  57. bytesParamType = &bq.QueryParameterType{Type: "BYTES"}
  58. dateParamType = &bq.QueryParameterType{Type: "DATE"}
  59. timeParamType = &bq.QueryParameterType{Type: "TIME"}
  60. dateTimeParamType = &bq.QueryParameterType{Type: "DATETIME"}
  61. timestampParamType = &bq.QueryParameterType{Type: "TIMESTAMP"}
  62. numericParamType = &bq.QueryParameterType{Type: "NUMERIC"}
  63. )
  64. var (
  65. typeOfDate = reflect.TypeOf(civil.Date{})
  66. typeOfTime = reflect.TypeOf(civil.Time{})
  67. typeOfDateTime = reflect.TypeOf(civil.DateTime{})
  68. typeOfGoTime = reflect.TypeOf(time.Time{})
  69. typeOfRat = reflect.TypeOf(&big.Rat{})
  70. )
  71. // A QueryParameter is a parameter to a query.
  72. type QueryParameter struct {
  73. // Name is used for named parameter mode.
  74. // It must match the name in the query case-insensitively.
  75. Name string
  76. // Value is the value of the parameter.
  77. //
  78. // When you create a QueryParameter to send to BigQuery, the following Go types
  79. // are supported, with their corresponding Bigquery types:
  80. // int, int8, int16, int32, int64, uint8, uint16, uint32: INT64
  81. // Note that uint, uint64 and uintptr are not supported, because
  82. // they may contain values that cannot fit into a 64-bit signed integer.
  83. // float32, float64: FLOAT64
  84. // bool: BOOL
  85. // string: STRING
  86. // []byte: BYTES
  87. // time.Time: TIMESTAMP
  88. // *big.Rat: NUMERIC
  89. // Arrays and slices of the above.
  90. // Structs of the above. Only the exported fields are used.
  91. //
  92. // When a QueryParameter is returned inside a QueryConfig from a call to
  93. // Job.Config:
  94. // Integers are of type int64.
  95. // Floating-point values are of type float64.
  96. // Arrays are of type []interface{}, regardless of the array element type.
  97. // Structs are of type map[string]interface{}.
  98. Value interface{}
  99. }
  100. func (p QueryParameter) toBQ() (*bq.QueryParameter, error) {
  101. pv, err := paramValue(reflect.ValueOf(p.Value))
  102. if err != nil {
  103. return nil, err
  104. }
  105. pt, err := paramType(reflect.TypeOf(p.Value))
  106. if err != nil {
  107. return nil, err
  108. }
  109. return &bq.QueryParameter{
  110. Name: p.Name,
  111. ParameterValue: &pv,
  112. ParameterType: pt,
  113. }, nil
  114. }
  115. func paramType(t reflect.Type) (*bq.QueryParameterType, error) {
  116. if t == nil {
  117. return nil, errors.New("bigquery: nil parameter")
  118. }
  119. switch t {
  120. case typeOfDate:
  121. return dateParamType, nil
  122. case typeOfTime:
  123. return timeParamType, nil
  124. case typeOfDateTime:
  125. return dateTimeParamType, nil
  126. case typeOfGoTime:
  127. return timestampParamType, nil
  128. case typeOfRat:
  129. return numericParamType, nil
  130. }
  131. switch t.Kind() {
  132. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32:
  133. return int64ParamType, nil
  134. case reflect.Float32, reflect.Float64:
  135. return float64ParamType, nil
  136. case reflect.Bool:
  137. return boolParamType, nil
  138. case reflect.String:
  139. return stringParamType, nil
  140. case reflect.Slice:
  141. if t.Elem().Kind() == reflect.Uint8 {
  142. return bytesParamType, nil
  143. }
  144. fallthrough
  145. case reflect.Array:
  146. et, err := paramType(t.Elem())
  147. if err != nil {
  148. return nil, err
  149. }
  150. return &bq.QueryParameterType{Type: "ARRAY", ArrayType: et}, nil
  151. case reflect.Ptr:
  152. if t.Elem().Kind() != reflect.Struct {
  153. break
  154. }
  155. t = t.Elem()
  156. fallthrough
  157. case reflect.Struct:
  158. var fts []*bq.QueryParameterTypeStructTypes
  159. fields, err := fieldCache.Fields(t)
  160. if err != nil {
  161. return nil, err
  162. }
  163. for _, f := range fields {
  164. pt, err := paramType(f.Type)
  165. if err != nil {
  166. return nil, err
  167. }
  168. fts = append(fts, &bq.QueryParameterTypeStructTypes{
  169. Name: f.Name,
  170. Type: pt,
  171. })
  172. }
  173. return &bq.QueryParameterType{Type: "STRUCT", StructTypes: fts}, nil
  174. }
  175. return nil, fmt.Errorf("bigquery: Go type %s cannot be represented as a parameter type", t)
  176. }
  177. func paramValue(v reflect.Value) (bq.QueryParameterValue, error) {
  178. var res bq.QueryParameterValue
  179. if !v.IsValid() {
  180. return res, errors.New("bigquery: nil parameter")
  181. }
  182. t := v.Type()
  183. switch t {
  184. case typeOfDate:
  185. res.Value = v.Interface().(civil.Date).String()
  186. return res, nil
  187. case typeOfTime:
  188. // civil.Time has nanosecond resolution, but BigQuery TIME only microsecond.
  189. // (If we send nanoseconds, then when we try to read the result we get "query job
  190. // missing destination table").
  191. res.Value = CivilTimeString(v.Interface().(civil.Time))
  192. return res, nil
  193. case typeOfDateTime:
  194. res.Value = CivilDateTimeString(v.Interface().(civil.DateTime))
  195. return res, nil
  196. case typeOfGoTime:
  197. res.Value = v.Interface().(time.Time).Format(timestampFormat)
  198. return res, nil
  199. case typeOfRat:
  200. res.Value = NumericString(v.Interface().(*big.Rat))
  201. return res, nil
  202. }
  203. switch t.Kind() {
  204. case reflect.Slice:
  205. if t.Elem().Kind() == reflect.Uint8 {
  206. res.Value = base64.StdEncoding.EncodeToString(v.Interface().([]byte))
  207. return res, nil
  208. }
  209. fallthrough
  210. case reflect.Array:
  211. var vals []*bq.QueryParameterValue
  212. for i := 0; i < v.Len(); i++ {
  213. val, err := paramValue(v.Index(i))
  214. if err != nil {
  215. return bq.QueryParameterValue{}, err
  216. }
  217. vals = append(vals, &val)
  218. }
  219. return bq.QueryParameterValue{ArrayValues: vals}, nil
  220. case reflect.Ptr:
  221. if t.Elem().Kind() != reflect.Struct {
  222. return res, fmt.Errorf("bigquery: Go type %s cannot be represented as a parameter value", t)
  223. }
  224. t = t.Elem()
  225. v = v.Elem()
  226. if !v.IsValid() {
  227. // nil pointer becomes empty value
  228. return res, nil
  229. }
  230. fallthrough
  231. case reflect.Struct:
  232. fields, err := fieldCache.Fields(t)
  233. if err != nil {
  234. return bq.QueryParameterValue{}, err
  235. }
  236. res.StructValues = map[string]bq.QueryParameterValue{}
  237. for _, f := range fields {
  238. fv := v.FieldByIndex(f.Index)
  239. fp, err := paramValue(fv)
  240. if err != nil {
  241. return bq.QueryParameterValue{}, err
  242. }
  243. res.StructValues[f.Name] = fp
  244. }
  245. return res, nil
  246. }
  247. // None of the above: assume a scalar type. (If it's not a valid type,
  248. // paramType will catch the error.)
  249. res.Value = fmt.Sprint(v.Interface())
  250. return res, nil
  251. }
  252. func bqToQueryParameter(q *bq.QueryParameter) (QueryParameter, error) {
  253. p := QueryParameter{Name: q.Name}
  254. val, err := convertParamValue(q.ParameterValue, q.ParameterType)
  255. if err != nil {
  256. return QueryParameter{}, err
  257. }
  258. p.Value = val
  259. return p, nil
  260. }
  261. var paramTypeToFieldType = map[string]FieldType{
  262. int64ParamType.Type: IntegerFieldType,
  263. float64ParamType.Type: FloatFieldType,
  264. boolParamType.Type: BooleanFieldType,
  265. stringParamType.Type: StringFieldType,
  266. bytesParamType.Type: BytesFieldType,
  267. dateParamType.Type: DateFieldType,
  268. timeParamType.Type: TimeFieldType,
  269. numericParamType.Type: NumericFieldType,
  270. }
  271. // Convert a parameter value from the service to a Go value. This is similar to, but
  272. // not quite the same as, converting data values.
  273. func convertParamValue(qval *bq.QueryParameterValue, qtype *bq.QueryParameterType) (interface{}, error) {
  274. switch qtype.Type {
  275. case "ARRAY":
  276. if qval == nil {
  277. return []interface{}(nil), nil
  278. }
  279. return convertParamArray(qval.ArrayValues, qtype.ArrayType)
  280. case "STRUCT":
  281. if qval == nil {
  282. return map[string]interface{}(nil), nil
  283. }
  284. return convertParamStruct(qval.StructValues, qtype.StructTypes)
  285. case "TIMESTAMP":
  286. return time.Parse(timestampFormat, qval.Value)
  287. case "DATETIME":
  288. return parseCivilDateTime(qval.Value)
  289. default:
  290. return convertBasicType(qval.Value, paramTypeToFieldType[qtype.Type])
  291. }
  292. }
  293. // convertParamArray converts a query parameter array value to a Go value. It
  294. // always returns a []interface{}.
  295. func convertParamArray(elVals []*bq.QueryParameterValue, elType *bq.QueryParameterType) ([]interface{}, error) {
  296. var vals []interface{}
  297. for _, el := range elVals {
  298. val, err := convertParamValue(el, elType)
  299. if err != nil {
  300. return nil, err
  301. }
  302. vals = append(vals, val)
  303. }
  304. return vals, nil
  305. }
  306. // convertParamStruct converts a query parameter struct value into a Go value. It
  307. // always returns a map[string]interface{}.
  308. func convertParamStruct(sVals map[string]bq.QueryParameterValue, sTypes []*bq.QueryParameterTypeStructTypes) (map[string]interface{}, error) {
  309. vals := map[string]interface{}{}
  310. for _, st := range sTypes {
  311. if sv, ok := sVals[st.Name]; ok {
  312. val, err := convertParamValue(&sv, st.Type)
  313. if err != nil {
  314. return nil, err
  315. }
  316. vals[st.Name] = val
  317. } else {
  318. vals[st.Name] = nil
  319. }
  320. }
  321. return vals, nil
  322. }