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.
 
 
 

1045 lines
24 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. "math/big"
  18. "reflect"
  19. "testing"
  20. "time"
  21. "cloud.google.com/go/civil"
  22. "cloud.google.com/go/internal/pretty"
  23. "cloud.google.com/go/internal/testutil"
  24. bq "google.golang.org/api/bigquery/v2"
  25. )
  26. func (fs *FieldSchema) GoString() string {
  27. if fs == nil {
  28. return "<nil>"
  29. }
  30. return fmt.Sprintf("{Name:%s Description:%s Repeated:%t Required:%t Type:%s Schema:%s}",
  31. fs.Name,
  32. fs.Description,
  33. fs.Repeated,
  34. fs.Required,
  35. fs.Type,
  36. fmt.Sprintf("%#v", fs.Schema),
  37. )
  38. }
  39. func bqTableFieldSchema(desc, name, typ, mode string) *bq.TableFieldSchema {
  40. return &bq.TableFieldSchema{
  41. Description: desc,
  42. Name: name,
  43. Mode: mode,
  44. Type: typ,
  45. }
  46. }
  47. func fieldSchema(desc, name, typ string, repeated, required bool) *FieldSchema {
  48. return &FieldSchema{
  49. Description: desc,
  50. Name: name,
  51. Repeated: repeated,
  52. Required: required,
  53. Type: FieldType(typ),
  54. }
  55. }
  56. func TestSchemaConversion(t *testing.T) {
  57. testCases := []struct {
  58. schema Schema
  59. bqSchema *bq.TableSchema
  60. }{
  61. {
  62. // required
  63. bqSchema: &bq.TableSchema{
  64. Fields: []*bq.TableFieldSchema{
  65. bqTableFieldSchema("desc", "name", "STRING", "REQUIRED"),
  66. },
  67. },
  68. schema: Schema{
  69. fieldSchema("desc", "name", "STRING", false, true),
  70. },
  71. },
  72. {
  73. // repeated
  74. bqSchema: &bq.TableSchema{
  75. Fields: []*bq.TableFieldSchema{
  76. bqTableFieldSchema("desc", "name", "STRING", "REPEATED"),
  77. },
  78. },
  79. schema: Schema{
  80. fieldSchema("desc", "name", "STRING", true, false),
  81. },
  82. },
  83. {
  84. // nullable, string
  85. bqSchema: &bq.TableSchema{
  86. Fields: []*bq.TableFieldSchema{
  87. bqTableFieldSchema("desc", "name", "STRING", ""),
  88. },
  89. },
  90. schema: Schema{
  91. fieldSchema("desc", "name", "STRING", false, false),
  92. },
  93. },
  94. {
  95. // integer
  96. bqSchema: &bq.TableSchema{
  97. Fields: []*bq.TableFieldSchema{
  98. bqTableFieldSchema("desc", "name", "INTEGER", ""),
  99. },
  100. },
  101. schema: Schema{
  102. fieldSchema("desc", "name", "INTEGER", false, false),
  103. },
  104. },
  105. {
  106. // float
  107. bqSchema: &bq.TableSchema{
  108. Fields: []*bq.TableFieldSchema{
  109. bqTableFieldSchema("desc", "name", "FLOAT", ""),
  110. },
  111. },
  112. schema: Schema{
  113. fieldSchema("desc", "name", "FLOAT", false, false),
  114. },
  115. },
  116. {
  117. // boolean
  118. bqSchema: &bq.TableSchema{
  119. Fields: []*bq.TableFieldSchema{
  120. bqTableFieldSchema("desc", "name", "BOOLEAN", ""),
  121. },
  122. },
  123. schema: Schema{
  124. fieldSchema("desc", "name", "BOOLEAN", false, false),
  125. },
  126. },
  127. {
  128. // timestamp
  129. bqSchema: &bq.TableSchema{
  130. Fields: []*bq.TableFieldSchema{
  131. bqTableFieldSchema("desc", "name", "TIMESTAMP", ""),
  132. },
  133. },
  134. schema: Schema{
  135. fieldSchema("desc", "name", "TIMESTAMP", false, false),
  136. },
  137. },
  138. {
  139. // civil times
  140. bqSchema: &bq.TableSchema{
  141. Fields: []*bq.TableFieldSchema{
  142. bqTableFieldSchema("desc", "f1", "TIME", ""),
  143. bqTableFieldSchema("desc", "f2", "DATE", ""),
  144. bqTableFieldSchema("desc", "f3", "DATETIME", ""),
  145. },
  146. },
  147. schema: Schema{
  148. fieldSchema("desc", "f1", "TIME", false, false),
  149. fieldSchema("desc", "f2", "DATE", false, false),
  150. fieldSchema("desc", "f3", "DATETIME", false, false),
  151. },
  152. },
  153. {
  154. // numeric
  155. bqSchema: &bq.TableSchema{
  156. Fields: []*bq.TableFieldSchema{
  157. bqTableFieldSchema("desc", "n", "NUMERIC", ""),
  158. },
  159. },
  160. schema: Schema{
  161. fieldSchema("desc", "n", "NUMERIC", false, false),
  162. },
  163. },
  164. {
  165. bqSchema: &bq.TableSchema{
  166. Fields: []*bq.TableFieldSchema{
  167. bqTableFieldSchema("geo", "g", "GEOGRAPHY", ""),
  168. },
  169. },
  170. schema: Schema{
  171. fieldSchema("geo", "g", "GEOGRAPHY", false, false),
  172. },
  173. },
  174. {
  175. // nested
  176. bqSchema: &bq.TableSchema{
  177. Fields: []*bq.TableFieldSchema{
  178. {
  179. Description: "An outer schema wrapping a nested schema",
  180. Name: "outer",
  181. Mode: "REQUIRED",
  182. Type: "RECORD",
  183. Fields: []*bq.TableFieldSchema{
  184. bqTableFieldSchema("inner field", "inner", "STRING", ""),
  185. },
  186. },
  187. },
  188. },
  189. schema: Schema{
  190. &FieldSchema{
  191. Description: "An outer schema wrapping a nested schema",
  192. Name: "outer",
  193. Required: true,
  194. Type: "RECORD",
  195. Schema: Schema{
  196. {
  197. Description: "inner field",
  198. Name: "inner",
  199. Type: "STRING",
  200. },
  201. },
  202. },
  203. },
  204. },
  205. }
  206. for _, tc := range testCases {
  207. bqSchema := tc.schema.toBQ()
  208. if !testutil.Equal(bqSchema, tc.bqSchema) {
  209. t.Errorf("converting to TableSchema: got:\n%v\nwant:\n%v",
  210. pretty.Value(bqSchema), pretty.Value(tc.bqSchema))
  211. }
  212. schema := bqToSchema(tc.bqSchema)
  213. if !testutil.Equal(schema, tc.schema) {
  214. t.Errorf("converting to Schema: got:\n%v\nwant:\n%v", schema, tc.schema)
  215. }
  216. }
  217. }
  218. type allStrings struct {
  219. String string
  220. ByteSlice []byte
  221. }
  222. type allSignedIntegers struct {
  223. Int64 int64
  224. Int32 int32
  225. Int16 int16
  226. Int8 int8
  227. Int int
  228. }
  229. type allUnsignedIntegers struct {
  230. Uint32 uint32
  231. Uint16 uint16
  232. Uint8 uint8
  233. }
  234. type allFloat struct {
  235. Float64 float64
  236. Float32 float32
  237. // NOTE: Complex32 and Complex64 are unsupported by BigQuery
  238. }
  239. type allBoolean struct {
  240. Bool bool
  241. }
  242. type allTime struct {
  243. Timestamp time.Time
  244. Time civil.Time
  245. Date civil.Date
  246. DateTime civil.DateTime
  247. }
  248. type allNumeric struct {
  249. Numeric *big.Rat
  250. }
  251. func reqField(name, typ string) *FieldSchema {
  252. return &FieldSchema{
  253. Name: name,
  254. Type: FieldType(typ),
  255. Required: true,
  256. }
  257. }
  258. func optField(name, typ string) *FieldSchema {
  259. return &FieldSchema{
  260. Name: name,
  261. Type: FieldType(typ),
  262. Required: false,
  263. }
  264. }
  265. func TestSimpleInference(t *testing.T) {
  266. testCases := []struct {
  267. in interface{}
  268. want Schema
  269. }{
  270. {
  271. in: allSignedIntegers{},
  272. want: Schema{
  273. reqField("Int64", "INTEGER"),
  274. reqField("Int32", "INTEGER"),
  275. reqField("Int16", "INTEGER"),
  276. reqField("Int8", "INTEGER"),
  277. reqField("Int", "INTEGER"),
  278. },
  279. },
  280. {
  281. in: allUnsignedIntegers{},
  282. want: Schema{
  283. reqField("Uint32", "INTEGER"),
  284. reqField("Uint16", "INTEGER"),
  285. reqField("Uint8", "INTEGER"),
  286. },
  287. },
  288. {
  289. in: allFloat{},
  290. want: Schema{
  291. reqField("Float64", "FLOAT"),
  292. reqField("Float32", "FLOAT"),
  293. },
  294. },
  295. {
  296. in: allBoolean{},
  297. want: Schema{
  298. reqField("Bool", "BOOLEAN"),
  299. },
  300. },
  301. {
  302. in: &allBoolean{},
  303. want: Schema{
  304. reqField("Bool", "BOOLEAN"),
  305. },
  306. },
  307. {
  308. in: allTime{},
  309. want: Schema{
  310. reqField("Timestamp", "TIMESTAMP"),
  311. reqField("Time", "TIME"),
  312. reqField("Date", "DATE"),
  313. reqField("DateTime", "DATETIME"),
  314. },
  315. },
  316. {
  317. in: &allNumeric{},
  318. want: Schema{
  319. reqField("Numeric", "NUMERIC"),
  320. },
  321. },
  322. {
  323. in: allStrings{},
  324. want: Schema{
  325. reqField("String", "STRING"),
  326. reqField("ByteSlice", "BYTES"),
  327. },
  328. },
  329. }
  330. for _, tc := range testCases {
  331. got, err := InferSchema(tc.in)
  332. if err != nil {
  333. t.Fatalf("%T: error inferring TableSchema: %v", tc.in, err)
  334. }
  335. if !testutil.Equal(got, tc.want) {
  336. t.Errorf("%T: inferring TableSchema: got:\n%#v\nwant:\n%#v", tc.in,
  337. pretty.Value(got), pretty.Value(tc.want))
  338. }
  339. }
  340. }
  341. type containsNested struct {
  342. NotNested int
  343. Nested struct {
  344. Inside int
  345. }
  346. }
  347. type containsDoubleNested struct {
  348. NotNested int
  349. Nested struct {
  350. InsideNested struct {
  351. Inside int
  352. }
  353. }
  354. }
  355. type ptrNested struct {
  356. Ptr *struct{ Inside int }
  357. }
  358. type dup struct { // more than one field of the same struct type
  359. A, B allBoolean
  360. }
  361. func TestNestedInference(t *testing.T) {
  362. testCases := []struct {
  363. in interface{}
  364. want Schema
  365. }{
  366. {
  367. in: containsNested{},
  368. want: Schema{
  369. reqField("NotNested", "INTEGER"),
  370. &FieldSchema{
  371. Name: "Nested",
  372. Required: true,
  373. Type: "RECORD",
  374. Schema: Schema{reqField("Inside", "INTEGER")},
  375. },
  376. },
  377. },
  378. {
  379. in: containsDoubleNested{},
  380. want: Schema{
  381. reqField("NotNested", "INTEGER"),
  382. &FieldSchema{
  383. Name: "Nested",
  384. Required: true,
  385. Type: "RECORD",
  386. Schema: Schema{
  387. {
  388. Name: "InsideNested",
  389. Required: true,
  390. Type: "RECORD",
  391. Schema: Schema{reqField("Inside", "INTEGER")},
  392. },
  393. },
  394. },
  395. },
  396. },
  397. {
  398. in: ptrNested{},
  399. want: Schema{
  400. &FieldSchema{
  401. Name: "Ptr",
  402. Required: true,
  403. Type: "RECORD",
  404. Schema: Schema{reqField("Inside", "INTEGER")},
  405. },
  406. },
  407. },
  408. {
  409. in: dup{},
  410. want: Schema{
  411. &FieldSchema{
  412. Name: "A",
  413. Required: true,
  414. Type: "RECORD",
  415. Schema: Schema{reqField("Bool", "BOOLEAN")},
  416. },
  417. &FieldSchema{
  418. Name: "B",
  419. Required: true,
  420. Type: "RECORD",
  421. Schema: Schema{reqField("Bool", "BOOLEAN")},
  422. },
  423. },
  424. },
  425. }
  426. for _, tc := range testCases {
  427. got, err := InferSchema(tc.in)
  428. if err != nil {
  429. t.Fatalf("%T: error inferring TableSchema: %v", tc.in, err)
  430. }
  431. if !testutil.Equal(got, tc.want) {
  432. t.Errorf("%T: inferring TableSchema: got:\n%#v\nwant:\n%#v", tc.in,
  433. pretty.Value(got), pretty.Value(tc.want))
  434. }
  435. }
  436. }
  437. type repeated struct {
  438. NotRepeated []byte
  439. RepeatedByteSlice [][]byte
  440. Slice []int
  441. Array [5]bool
  442. }
  443. type nestedRepeated struct {
  444. NotRepeated int
  445. Repeated []struct {
  446. Inside int
  447. }
  448. RepeatedPtr []*struct{ Inside int }
  449. }
  450. func repField(name, typ string) *FieldSchema {
  451. return &FieldSchema{
  452. Name: name,
  453. Type: FieldType(typ),
  454. Repeated: true,
  455. }
  456. }
  457. func TestRepeatedInference(t *testing.T) {
  458. testCases := []struct {
  459. in interface{}
  460. want Schema
  461. }{
  462. {
  463. in: repeated{},
  464. want: Schema{
  465. reqField("NotRepeated", "BYTES"),
  466. repField("RepeatedByteSlice", "BYTES"),
  467. repField("Slice", "INTEGER"),
  468. repField("Array", "BOOLEAN"),
  469. },
  470. },
  471. {
  472. in: nestedRepeated{},
  473. want: Schema{
  474. reqField("NotRepeated", "INTEGER"),
  475. {
  476. Name: "Repeated",
  477. Repeated: true,
  478. Type: "RECORD",
  479. Schema: Schema{reqField("Inside", "INTEGER")},
  480. },
  481. {
  482. Name: "RepeatedPtr",
  483. Repeated: true,
  484. Type: "RECORD",
  485. Schema: Schema{reqField("Inside", "INTEGER")},
  486. },
  487. },
  488. },
  489. }
  490. for i, tc := range testCases {
  491. got, err := InferSchema(tc.in)
  492. if err != nil {
  493. t.Fatalf("%d: error inferring TableSchema: %v", i, err)
  494. }
  495. if !testutil.Equal(got, tc.want) {
  496. t.Errorf("%d: inferring TableSchema: got:\n%#v\nwant:\n%#v", i,
  497. pretty.Value(got), pretty.Value(tc.want))
  498. }
  499. }
  500. }
  501. type allNulls struct {
  502. A NullInt64
  503. B NullFloat64
  504. C NullBool
  505. D NullString
  506. E NullTimestamp
  507. F NullTime
  508. G NullDate
  509. H NullDateTime
  510. I NullGeography
  511. }
  512. func TestNullInference(t *testing.T) {
  513. got, err := InferSchema(allNulls{})
  514. if err != nil {
  515. t.Fatal(err)
  516. }
  517. want := Schema{
  518. optField("A", "INTEGER"),
  519. optField("B", "FLOAT"),
  520. optField("C", "BOOLEAN"),
  521. optField("D", "STRING"),
  522. optField("E", "TIMESTAMP"),
  523. optField("F", "TIME"),
  524. optField("G", "DATE"),
  525. optField("H", "DATETIME"),
  526. optField("I", "GEOGRAPHY"),
  527. }
  528. if diff := testutil.Diff(got, want); diff != "" {
  529. t.Error(diff)
  530. }
  531. }
  532. type Embedded struct {
  533. Embedded int
  534. }
  535. type embedded struct {
  536. Embedded2 int
  537. }
  538. type nestedEmbedded struct {
  539. Embedded
  540. embedded
  541. }
  542. func TestEmbeddedInference(t *testing.T) {
  543. got, err := InferSchema(nestedEmbedded{})
  544. if err != nil {
  545. t.Fatal(err)
  546. }
  547. want := Schema{
  548. reqField("Embedded", "INTEGER"),
  549. reqField("Embedded2", "INTEGER"),
  550. }
  551. if !testutil.Equal(got, want) {
  552. t.Errorf("got %v, want %v", pretty.Value(got), pretty.Value(want))
  553. }
  554. }
  555. func TestRecursiveInference(t *testing.T) {
  556. type List struct {
  557. Val int
  558. Next *List
  559. }
  560. _, err := InferSchema(List{})
  561. if err == nil {
  562. t.Fatal("got nil, want error")
  563. }
  564. }
  565. type withTags struct {
  566. NoTag int
  567. ExcludeTag int `bigquery:"-"`
  568. SimpleTag int `bigquery:"simple_tag"`
  569. UnderscoreTag int `bigquery:"_id"`
  570. MixedCase int `bigquery:"MIXEDcase"`
  571. Nullable []byte `bigquery:",nullable"`
  572. NullNumeric *big.Rat `bigquery:",nullable"`
  573. }
  574. type withTagsNested struct {
  575. Nested withTags `bigquery:"nested"`
  576. NestedAnonymous struct {
  577. ExcludeTag int `bigquery:"-"`
  578. Inside int `bigquery:"inside"`
  579. } `bigquery:"anon"`
  580. PNested *struct{ X int } // not nullable, for backwards compatibility
  581. PNestedNullable *struct{ X int } `bigquery:",nullable"`
  582. }
  583. type withTagsRepeated struct {
  584. Repeated []withTags `bigquery:"repeated"`
  585. RepeatedAnonymous []struct {
  586. ExcludeTag int `bigquery:"-"`
  587. Inside int `bigquery:"inside"`
  588. } `bigquery:"anon"`
  589. }
  590. type withTagsEmbedded struct {
  591. withTags
  592. }
  593. var withTagsSchema = Schema{
  594. reqField("NoTag", "INTEGER"),
  595. reqField("simple_tag", "INTEGER"),
  596. reqField("_id", "INTEGER"),
  597. reqField("MIXEDcase", "INTEGER"),
  598. optField("Nullable", "BYTES"),
  599. optField("NullNumeric", "NUMERIC"),
  600. }
  601. func TestTagInference(t *testing.T) {
  602. testCases := []struct {
  603. in interface{}
  604. want Schema
  605. }{
  606. {
  607. in: withTags{},
  608. want: withTagsSchema,
  609. },
  610. {
  611. in: withTagsNested{},
  612. want: Schema{
  613. &FieldSchema{
  614. Name: "nested",
  615. Required: true,
  616. Type: "RECORD",
  617. Schema: withTagsSchema,
  618. },
  619. &FieldSchema{
  620. Name: "anon",
  621. Required: true,
  622. Type: "RECORD",
  623. Schema: Schema{reqField("inside", "INTEGER")},
  624. },
  625. &FieldSchema{
  626. Name: "PNested",
  627. Required: true,
  628. Type: "RECORD",
  629. Schema: Schema{reqField("X", "INTEGER")},
  630. },
  631. &FieldSchema{
  632. Name: "PNestedNullable",
  633. Required: false,
  634. Type: "RECORD",
  635. Schema: Schema{reqField("X", "INTEGER")},
  636. },
  637. },
  638. },
  639. {
  640. in: withTagsRepeated{},
  641. want: Schema{
  642. &FieldSchema{
  643. Name: "repeated",
  644. Repeated: true,
  645. Type: "RECORD",
  646. Schema: withTagsSchema,
  647. },
  648. &FieldSchema{
  649. Name: "anon",
  650. Repeated: true,
  651. Type: "RECORD",
  652. Schema: Schema{reqField("inside", "INTEGER")},
  653. },
  654. },
  655. },
  656. {
  657. in: withTagsEmbedded{},
  658. want: withTagsSchema,
  659. },
  660. }
  661. for i, tc := range testCases {
  662. got, err := InferSchema(tc.in)
  663. if err != nil {
  664. t.Fatalf("%d: error inferring TableSchema: %v", i, err)
  665. }
  666. if !testutil.Equal(got, tc.want) {
  667. t.Errorf("%d: inferring TableSchema: got:\n%#v\nwant:\n%#v", i,
  668. pretty.Value(got), pretty.Value(tc.want))
  669. }
  670. }
  671. }
  672. func TestTagInferenceErrors(t *testing.T) {
  673. testCases := []interface{}{
  674. struct {
  675. LongTag int `bigquery:"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy"`
  676. }{},
  677. struct {
  678. UnsupporedStartChar int `bigquery:"øab"`
  679. }{},
  680. struct {
  681. UnsupportedEndChar int `bigquery:"abø"`
  682. }{},
  683. struct {
  684. UnsupportedMiddleChar int `bigquery:"aøb"`
  685. }{},
  686. struct {
  687. StartInt int `bigquery:"1abc"`
  688. }{},
  689. struct {
  690. Hyphens int `bigquery:"a-b"`
  691. }{},
  692. }
  693. for i, tc := range testCases {
  694. _, got := InferSchema(tc)
  695. if _, ok := got.(invalidFieldNameError); !ok {
  696. t.Errorf("%d: inferring TableSchema: got:\n%#v\nwant invalidFieldNameError", i, got)
  697. }
  698. }
  699. _, err := InferSchema(struct {
  700. X int `bigquery:",optional"`
  701. }{})
  702. if err == nil {
  703. t.Error("got nil, want error")
  704. }
  705. }
  706. func TestSchemaErrors(t *testing.T) {
  707. testCases := []struct {
  708. in interface{}
  709. want interface{}
  710. }{
  711. {
  712. in: []byte{},
  713. want: noStructError{},
  714. },
  715. {
  716. in: new(int),
  717. want: noStructError{},
  718. },
  719. {
  720. in: struct{ Uint uint }{},
  721. want: unsupportedFieldTypeError{},
  722. },
  723. {
  724. in: struct{ Uint64 uint64 }{},
  725. want: unsupportedFieldTypeError{},
  726. },
  727. {
  728. in: struct{ Uintptr uintptr }{},
  729. want: unsupportedFieldTypeError{},
  730. },
  731. {
  732. in: struct{ Complex complex64 }{},
  733. want: unsupportedFieldTypeError{},
  734. },
  735. {
  736. in: struct{ Map map[string]int }{},
  737. want: unsupportedFieldTypeError{},
  738. },
  739. {
  740. in: struct{ Chan chan bool }{},
  741. want: unsupportedFieldTypeError{},
  742. },
  743. {
  744. in: struct{ Ptr *int }{},
  745. want: unsupportedFieldTypeError{},
  746. },
  747. {
  748. in: struct{ Interface interface{} }{},
  749. want: unsupportedFieldTypeError{},
  750. },
  751. {
  752. in: struct{ MultiDimensional [][]int }{},
  753. want: unsupportedFieldTypeError{},
  754. },
  755. {
  756. in: struct{ MultiDimensional [][][]byte }{},
  757. want: unsupportedFieldTypeError{},
  758. },
  759. {
  760. in: struct{ SliceOfPointer []*int }{},
  761. want: unsupportedFieldTypeError{},
  762. },
  763. {
  764. in: struct{ SliceOfNull []NullInt64 }{},
  765. want: unsupportedFieldTypeError{},
  766. },
  767. {
  768. in: struct{ ChanSlice []chan bool }{},
  769. want: unsupportedFieldTypeError{},
  770. },
  771. {
  772. in: struct{ NestedChan struct{ Chan []chan bool } }{},
  773. want: unsupportedFieldTypeError{},
  774. },
  775. {
  776. in: struct {
  777. X int `bigquery:",nullable"`
  778. }{},
  779. want: badNullableError{},
  780. },
  781. {
  782. in: struct {
  783. X bool `bigquery:",nullable"`
  784. }{},
  785. want: badNullableError{},
  786. },
  787. {
  788. in: struct {
  789. X struct{ N int } `bigquery:",nullable"`
  790. }{},
  791. want: badNullableError{},
  792. },
  793. {
  794. in: struct {
  795. X []int `bigquery:",nullable"`
  796. }{},
  797. want: badNullableError{},
  798. },
  799. {
  800. in: struct{ X *[]byte }{},
  801. want: unsupportedFieldTypeError{},
  802. },
  803. {
  804. in: struct{ X *[]int }{},
  805. want: unsupportedFieldTypeError{},
  806. },
  807. {
  808. in: struct{ X *int }{},
  809. want: unsupportedFieldTypeError{},
  810. },
  811. }
  812. for _, tc := range testCases {
  813. _, got := InferSchema(tc.in)
  814. if reflect.TypeOf(got) != reflect.TypeOf(tc.want) {
  815. t.Errorf("%#v: got:\n%#v\nwant type %T", tc.in, got, tc.want)
  816. }
  817. }
  818. }
  819. func TestHasRecursiveType(t *testing.T) {
  820. type (
  821. nonStruct int
  822. nonRec struct{ A string }
  823. dup struct{ A, B nonRec }
  824. rec struct {
  825. A int
  826. B *rec
  827. }
  828. recUnexported struct {
  829. A int
  830. }
  831. hasRec struct {
  832. A int
  833. R *rec
  834. }
  835. recSlicePointer struct {
  836. A []*recSlicePointer
  837. }
  838. )
  839. for _, test := range []struct {
  840. in interface{}
  841. want bool
  842. }{
  843. {nonStruct(0), false},
  844. {nonRec{}, false},
  845. {dup{}, false},
  846. {rec{}, true},
  847. {recUnexported{}, false},
  848. {hasRec{}, true},
  849. {&recSlicePointer{}, true},
  850. } {
  851. got, err := hasRecursiveType(reflect.TypeOf(test.in), nil)
  852. if err != nil {
  853. t.Fatal(err)
  854. }
  855. if got != test.want {
  856. t.Errorf("%T: got %t, want %t", test.in, got, test.want)
  857. }
  858. }
  859. }
  860. func TestSchemaFromJSON(t *testing.T) {
  861. testCasesExpectingSuccess := []struct {
  862. bqSchemaJSON []byte
  863. description string
  864. expectedSchema Schema
  865. }{
  866. {
  867. description: "Flat table with a mixture of NULLABLE and REQUIRED fields",
  868. bqSchemaJSON: []byte(`
  869. [
  870. {"name":"flat_string","type":"STRING","mode":"NULLABLE","description":"Flat nullable string"},
  871. {"name":"flat_bytes","type":"BYTES","mode":"REQUIRED","description":"Flat required BYTES"},
  872. {"name":"flat_integer","type":"INTEGER","mode":"NULLABLE","description":"Flat nullable INTEGER"},
  873. {"name":"flat_float","type":"FLOAT","mode":"REQUIRED","description":"Flat required FLOAT"},
  874. {"name":"flat_boolean","type":"BOOLEAN","mode":"NULLABLE","description":"Flat nullable BOOLEAN"},
  875. {"name":"flat_timestamp","type":"TIMESTAMP","mode":"REQUIRED","description":"Flat required TIMESTAMP"},
  876. {"name":"flat_date","type":"DATE","mode":"NULLABLE","description":"Flat required DATE"},
  877. {"name":"flat_time","type":"TIME","mode":"REQUIRED","description":"Flat nullable TIME"},
  878. {"name":"flat_datetime","type":"DATETIME","mode":"NULLABLE","description":"Flat required DATETIME"},
  879. {"name":"flat_numeric","type":"NUMERIC","mode":"REQUIRED","description":"Flat nullable NUMERIC"},
  880. {"name":"flat_geography","type":"GEOGRAPHY","mode":"REQUIRED","description":"Flat required GEOGRAPHY"}
  881. ]`),
  882. expectedSchema: Schema{
  883. fieldSchema("Flat nullable string", "flat_string", "STRING", false, false),
  884. fieldSchema("Flat required BYTES", "flat_bytes", "BYTES", false, true),
  885. fieldSchema("Flat nullable INTEGER", "flat_integer", "INTEGER", false, false),
  886. fieldSchema("Flat required FLOAT", "flat_float", "FLOAT", false, true),
  887. fieldSchema("Flat nullable BOOLEAN", "flat_boolean", "BOOLEAN", false, false),
  888. fieldSchema("Flat required TIMESTAMP", "flat_timestamp", "TIMESTAMP", false, true),
  889. fieldSchema("Flat required DATE", "flat_date", "DATE", false, false),
  890. fieldSchema("Flat nullable TIME", "flat_time", "TIME", false, true),
  891. fieldSchema("Flat required DATETIME", "flat_datetime", "DATETIME", false, false),
  892. fieldSchema("Flat nullable NUMERIC", "flat_numeric", "NUMERIC", false, true),
  893. fieldSchema("Flat required GEOGRAPHY", "flat_geography", "GEOGRAPHY", false, true),
  894. },
  895. },
  896. {
  897. description: "Table with a nested RECORD",
  898. bqSchemaJSON: []byte(`
  899. [
  900. {"name":"flat_string","type":"STRING","mode":"NULLABLE","description":"Flat nullable string"},
  901. {"name":"nested_record","type":"RECORD","mode":"NULLABLE","description":"Nested nullable RECORD","fields":[{"name":"record_field_1","type":"STRING","mode":"NULLABLE","description":"First nested record field"},{"name":"record_field_2","type":"INTEGER","mode":"REQUIRED","description":"Second nested record field"}]}
  902. ]`),
  903. expectedSchema: Schema{
  904. fieldSchema("Flat nullable string", "flat_string", "STRING", false, false),
  905. &FieldSchema{
  906. Description: "Nested nullable RECORD",
  907. Name: "nested_record",
  908. Required: false,
  909. Type: "RECORD",
  910. Schema: Schema{
  911. {
  912. Description: "First nested record field",
  913. Name: "record_field_1",
  914. Required: false,
  915. Type: "STRING",
  916. },
  917. {
  918. Description: "Second nested record field",
  919. Name: "record_field_2",
  920. Required: true,
  921. Type: "INTEGER",
  922. },
  923. },
  924. },
  925. },
  926. },
  927. {
  928. description: "Table with a repeated RECORD",
  929. bqSchemaJSON: []byte(`
  930. [
  931. {"name":"flat_string","type":"STRING","mode":"NULLABLE","description":"Flat nullable string"},
  932. {"name":"nested_record","type":"RECORD","mode":"REPEATED","description":"Nested nullable RECORD","fields":[{"name":"record_field_1","type":"STRING","mode":"NULLABLE","description":"First nested record field"},{"name":"record_field_2","type":"INTEGER","mode":"REQUIRED","description":"Second nested record field"}]}
  933. ]`),
  934. expectedSchema: Schema{
  935. fieldSchema("Flat nullable string", "flat_string", "STRING", false, false),
  936. &FieldSchema{
  937. Description: "Nested nullable RECORD",
  938. Name: "nested_record",
  939. Repeated: true,
  940. Required: false,
  941. Type: "RECORD",
  942. Schema: Schema{
  943. {
  944. Description: "First nested record field",
  945. Name: "record_field_1",
  946. Required: false,
  947. Type: "STRING",
  948. },
  949. {
  950. Description: "Second nested record field",
  951. Name: "record_field_2",
  952. Required: true,
  953. Type: "INTEGER",
  954. },
  955. },
  956. },
  957. },
  958. },
  959. }
  960. for _, tc := range testCasesExpectingSuccess {
  961. convertedSchema, err := SchemaFromJSON(tc.bqSchemaJSON)
  962. if err != nil {
  963. t.Errorf("encountered an error when converting JSON table schema (%s): %v", tc.description, err)
  964. continue
  965. }
  966. if !testutil.Equal(convertedSchema, tc.expectedSchema) {
  967. t.Errorf("generated JSON table schema (%s) differs from the expected schema", tc.description)
  968. }
  969. }
  970. testCasesExpectingFailure := []struct {
  971. bqSchemaJSON []byte
  972. description string
  973. }{
  974. {
  975. description: "Schema with invalid JSON",
  976. bqSchemaJSON: []byte(`This is not JSON`),
  977. },
  978. {
  979. description: "Schema with unknown field type",
  980. bqSchemaJSON: []byte(`[{"name":"strange_type","type":"STRANGE","description":"This type should not exist"}]`),
  981. },
  982. {
  983. description: "Schema with zero length",
  984. bqSchemaJSON: []byte(``),
  985. },
  986. }
  987. for _, tc := range testCasesExpectingFailure {
  988. _, err := SchemaFromJSON(tc.bqSchemaJSON)
  989. if err == nil {
  990. t.Errorf("converting this schema should have returned an error (%s): %v", tc.description, err)
  991. continue
  992. }
  993. }
  994. }