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.
 
 
 

921 lines
18 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. // nested
  166. bqSchema: &bq.TableSchema{
  167. Fields: []*bq.TableFieldSchema{
  168. {
  169. Description: "An outer schema wrapping a nested schema",
  170. Name: "outer",
  171. Mode: "REQUIRED",
  172. Type: "RECORD",
  173. Fields: []*bq.TableFieldSchema{
  174. bqTableFieldSchema("inner field", "inner", "STRING", ""),
  175. },
  176. },
  177. },
  178. },
  179. schema: Schema{
  180. &FieldSchema{
  181. Description: "An outer schema wrapping a nested schema",
  182. Name: "outer",
  183. Required: true,
  184. Type: "RECORD",
  185. Schema: Schema{
  186. {
  187. Description: "inner field",
  188. Name: "inner",
  189. Type: "STRING",
  190. },
  191. },
  192. },
  193. },
  194. },
  195. }
  196. for _, tc := range testCases {
  197. bqSchema := tc.schema.toBQ()
  198. if !testutil.Equal(bqSchema, tc.bqSchema) {
  199. t.Errorf("converting to TableSchema: got:\n%v\nwant:\n%v",
  200. pretty.Value(bqSchema), pretty.Value(tc.bqSchema))
  201. }
  202. schema := bqToSchema(tc.bqSchema)
  203. if !testutil.Equal(schema, tc.schema) {
  204. t.Errorf("converting to Schema: got:\n%v\nwant:\n%v", schema, tc.schema)
  205. }
  206. }
  207. }
  208. type allStrings struct {
  209. String string
  210. ByteSlice []byte
  211. }
  212. type allSignedIntegers struct {
  213. Int64 int64
  214. Int32 int32
  215. Int16 int16
  216. Int8 int8
  217. Int int
  218. }
  219. type allUnsignedIntegers struct {
  220. Uint32 uint32
  221. Uint16 uint16
  222. Uint8 uint8
  223. }
  224. type allFloat struct {
  225. Float64 float64
  226. Float32 float32
  227. // NOTE: Complex32 and Complex64 are unsupported by BigQuery
  228. }
  229. type allBoolean struct {
  230. Bool bool
  231. }
  232. type allTime struct {
  233. Timestamp time.Time
  234. Time civil.Time
  235. Date civil.Date
  236. DateTime civil.DateTime
  237. }
  238. type allNumeric struct {
  239. Numeric *big.Rat
  240. }
  241. func reqField(name, typ string) *FieldSchema {
  242. return &FieldSchema{
  243. Name: name,
  244. Type: FieldType(typ),
  245. Required: true,
  246. }
  247. }
  248. func optField(name, typ string) *FieldSchema {
  249. return &FieldSchema{
  250. Name: name,
  251. Type: FieldType(typ),
  252. Required: false,
  253. }
  254. }
  255. func TestSimpleInference(t *testing.T) {
  256. testCases := []struct {
  257. in interface{}
  258. want Schema
  259. }{
  260. {
  261. in: allSignedIntegers{},
  262. want: Schema{
  263. reqField("Int64", "INTEGER"),
  264. reqField("Int32", "INTEGER"),
  265. reqField("Int16", "INTEGER"),
  266. reqField("Int8", "INTEGER"),
  267. reqField("Int", "INTEGER"),
  268. },
  269. },
  270. {
  271. in: allUnsignedIntegers{},
  272. want: Schema{
  273. reqField("Uint32", "INTEGER"),
  274. reqField("Uint16", "INTEGER"),
  275. reqField("Uint8", "INTEGER"),
  276. },
  277. },
  278. {
  279. in: allFloat{},
  280. want: Schema{
  281. reqField("Float64", "FLOAT"),
  282. reqField("Float32", "FLOAT"),
  283. },
  284. },
  285. {
  286. in: allBoolean{},
  287. want: Schema{
  288. reqField("Bool", "BOOLEAN"),
  289. },
  290. },
  291. {
  292. in: &allBoolean{},
  293. want: Schema{
  294. reqField("Bool", "BOOLEAN"),
  295. },
  296. },
  297. {
  298. in: allTime{},
  299. want: Schema{
  300. reqField("Timestamp", "TIMESTAMP"),
  301. reqField("Time", "TIME"),
  302. reqField("Date", "DATE"),
  303. reqField("DateTime", "DATETIME"),
  304. },
  305. },
  306. {
  307. in: &allNumeric{},
  308. want: Schema{
  309. reqField("Numeric", "NUMERIC"),
  310. },
  311. },
  312. {
  313. in: allStrings{},
  314. want: Schema{
  315. reqField("String", "STRING"),
  316. reqField("ByteSlice", "BYTES"),
  317. },
  318. },
  319. }
  320. for _, tc := range testCases {
  321. got, err := InferSchema(tc.in)
  322. if err != nil {
  323. t.Fatalf("%T: error inferring TableSchema: %v", tc.in, err)
  324. }
  325. if !testutil.Equal(got, tc.want) {
  326. t.Errorf("%T: inferring TableSchema: got:\n%#v\nwant:\n%#v", tc.in,
  327. pretty.Value(got), pretty.Value(tc.want))
  328. }
  329. }
  330. }
  331. type containsNested struct {
  332. hidden string
  333. NotNested int
  334. Nested struct {
  335. Inside int
  336. }
  337. }
  338. type containsDoubleNested struct {
  339. NotNested int
  340. Nested struct {
  341. InsideNested struct {
  342. Inside int
  343. }
  344. }
  345. }
  346. type ptrNested struct {
  347. Ptr *struct{ Inside int }
  348. }
  349. type dup struct { // more than one field of the same struct type
  350. A, B allBoolean
  351. }
  352. func TestNestedInference(t *testing.T) {
  353. testCases := []struct {
  354. in interface{}
  355. want Schema
  356. }{
  357. {
  358. in: containsNested{},
  359. want: Schema{
  360. reqField("NotNested", "INTEGER"),
  361. &FieldSchema{
  362. Name: "Nested",
  363. Required: true,
  364. Type: "RECORD",
  365. Schema: Schema{reqField("Inside", "INTEGER")},
  366. },
  367. },
  368. },
  369. {
  370. in: containsDoubleNested{},
  371. want: Schema{
  372. reqField("NotNested", "INTEGER"),
  373. &FieldSchema{
  374. Name: "Nested",
  375. Required: true,
  376. Type: "RECORD",
  377. Schema: Schema{
  378. {
  379. Name: "InsideNested",
  380. Required: true,
  381. Type: "RECORD",
  382. Schema: Schema{reqField("Inside", "INTEGER")},
  383. },
  384. },
  385. },
  386. },
  387. },
  388. {
  389. in: ptrNested{},
  390. want: Schema{
  391. &FieldSchema{
  392. Name: "Ptr",
  393. Required: true,
  394. Type: "RECORD",
  395. Schema: Schema{reqField("Inside", "INTEGER")},
  396. },
  397. },
  398. },
  399. {
  400. in: dup{},
  401. want: Schema{
  402. &FieldSchema{
  403. Name: "A",
  404. Required: true,
  405. Type: "RECORD",
  406. Schema: Schema{reqField("Bool", "BOOLEAN")},
  407. },
  408. &FieldSchema{
  409. Name: "B",
  410. Required: true,
  411. Type: "RECORD",
  412. Schema: Schema{reqField("Bool", "BOOLEAN")},
  413. },
  414. },
  415. },
  416. }
  417. for _, tc := range testCases {
  418. got, err := InferSchema(tc.in)
  419. if err != nil {
  420. t.Fatalf("%T: error inferring TableSchema: %v", tc.in, err)
  421. }
  422. if !testutil.Equal(got, tc.want) {
  423. t.Errorf("%T: inferring TableSchema: got:\n%#v\nwant:\n%#v", tc.in,
  424. pretty.Value(got), pretty.Value(tc.want))
  425. }
  426. }
  427. }
  428. type repeated struct {
  429. NotRepeated []byte
  430. RepeatedByteSlice [][]byte
  431. Slice []int
  432. Array [5]bool
  433. }
  434. type nestedRepeated struct {
  435. NotRepeated int
  436. Repeated []struct {
  437. Inside int
  438. }
  439. RepeatedPtr []*struct{ Inside int }
  440. }
  441. func repField(name, typ string) *FieldSchema {
  442. return &FieldSchema{
  443. Name: name,
  444. Type: FieldType(typ),
  445. Repeated: true,
  446. }
  447. }
  448. func TestRepeatedInference(t *testing.T) {
  449. testCases := []struct {
  450. in interface{}
  451. want Schema
  452. }{
  453. {
  454. in: repeated{},
  455. want: Schema{
  456. reqField("NotRepeated", "BYTES"),
  457. repField("RepeatedByteSlice", "BYTES"),
  458. repField("Slice", "INTEGER"),
  459. repField("Array", "BOOLEAN"),
  460. },
  461. },
  462. {
  463. in: nestedRepeated{},
  464. want: Schema{
  465. reqField("NotRepeated", "INTEGER"),
  466. {
  467. Name: "Repeated",
  468. Repeated: true,
  469. Type: "RECORD",
  470. Schema: Schema{reqField("Inside", "INTEGER")},
  471. },
  472. {
  473. Name: "RepeatedPtr",
  474. Repeated: true,
  475. Type: "RECORD",
  476. Schema: Schema{reqField("Inside", "INTEGER")},
  477. },
  478. },
  479. },
  480. }
  481. for i, tc := range testCases {
  482. got, err := InferSchema(tc.in)
  483. if err != nil {
  484. t.Fatalf("%d: error inferring TableSchema: %v", i, err)
  485. }
  486. if !testutil.Equal(got, tc.want) {
  487. t.Errorf("%d: inferring TableSchema: got:\n%#v\nwant:\n%#v", i,
  488. pretty.Value(got), pretty.Value(tc.want))
  489. }
  490. }
  491. }
  492. type allNulls struct {
  493. A NullInt64
  494. B NullFloat64
  495. C NullBool
  496. D NullString
  497. E NullTimestamp
  498. F NullTime
  499. G NullDate
  500. H NullDateTime
  501. }
  502. func TestNullInference(t *testing.T) {
  503. got, err := InferSchema(allNulls{})
  504. if err != nil {
  505. t.Fatal(err)
  506. }
  507. want := Schema{
  508. optField("A", "INTEGER"),
  509. optField("B", "FLOAT"),
  510. optField("C", "BOOLEAN"),
  511. optField("D", "STRING"),
  512. optField("E", "TIMESTAMP"),
  513. optField("F", "TIME"),
  514. optField("G", "DATE"),
  515. optField("H", "DATETIME"),
  516. }
  517. if diff := testutil.Diff(got, want); diff != "" {
  518. t.Error(diff)
  519. }
  520. }
  521. type Embedded struct {
  522. Embedded int
  523. }
  524. type embedded struct {
  525. Embedded2 int
  526. }
  527. type nestedEmbedded struct {
  528. Embedded
  529. embedded
  530. }
  531. func TestEmbeddedInference(t *testing.T) {
  532. got, err := InferSchema(nestedEmbedded{})
  533. if err != nil {
  534. t.Fatal(err)
  535. }
  536. want := Schema{
  537. reqField("Embedded", "INTEGER"),
  538. reqField("Embedded2", "INTEGER"),
  539. }
  540. if !testutil.Equal(got, want) {
  541. t.Errorf("got %v, want %v", pretty.Value(got), pretty.Value(want))
  542. }
  543. }
  544. func TestRecursiveInference(t *testing.T) {
  545. type List struct {
  546. Val int
  547. Next *List
  548. }
  549. _, err := InferSchema(List{})
  550. if err == nil {
  551. t.Fatal("got nil, want error")
  552. }
  553. }
  554. type withTags struct {
  555. NoTag int
  556. ExcludeTag int `bigquery:"-"`
  557. SimpleTag int `bigquery:"simple_tag"`
  558. UnderscoreTag int `bigquery:"_id"`
  559. MixedCase int `bigquery:"MIXEDcase"`
  560. Nullable []byte `bigquery:",nullable"`
  561. NullNumeric *big.Rat `bigquery:",nullable"`
  562. }
  563. type withTagsNested struct {
  564. Nested withTags `bigquery:"nested"`
  565. NestedAnonymous struct {
  566. ExcludeTag int `bigquery:"-"`
  567. Inside int `bigquery:"inside"`
  568. } `bigquery:"anon"`
  569. PNested *struct{ X int } // not nullable, for backwards compatibility
  570. PNestedNullable *struct{ X int } `bigquery:",nullable"`
  571. }
  572. type withTagsRepeated struct {
  573. Repeated []withTags `bigquery:"repeated"`
  574. RepeatedAnonymous []struct {
  575. ExcludeTag int `bigquery:"-"`
  576. Inside int `bigquery:"inside"`
  577. } `bigquery:"anon"`
  578. }
  579. type withTagsEmbedded struct {
  580. withTags
  581. }
  582. var withTagsSchema = Schema{
  583. reqField("NoTag", "INTEGER"),
  584. reqField("simple_tag", "INTEGER"),
  585. reqField("_id", "INTEGER"),
  586. reqField("MIXEDcase", "INTEGER"),
  587. optField("Nullable", "BYTES"),
  588. optField("NullNumeric", "NUMERIC"),
  589. }
  590. func TestTagInference(t *testing.T) {
  591. testCases := []struct {
  592. in interface{}
  593. want Schema
  594. }{
  595. {
  596. in: withTags{},
  597. want: withTagsSchema,
  598. },
  599. {
  600. in: withTagsNested{},
  601. want: Schema{
  602. &FieldSchema{
  603. Name: "nested",
  604. Required: true,
  605. Type: "RECORD",
  606. Schema: withTagsSchema,
  607. },
  608. &FieldSchema{
  609. Name: "anon",
  610. Required: true,
  611. Type: "RECORD",
  612. Schema: Schema{reqField("inside", "INTEGER")},
  613. },
  614. &FieldSchema{
  615. Name: "PNested",
  616. Required: true,
  617. Type: "RECORD",
  618. Schema: Schema{reqField("X", "INTEGER")},
  619. },
  620. &FieldSchema{
  621. Name: "PNestedNullable",
  622. Required: false,
  623. Type: "RECORD",
  624. Schema: Schema{reqField("X", "INTEGER")},
  625. },
  626. },
  627. },
  628. {
  629. in: withTagsRepeated{},
  630. want: Schema{
  631. &FieldSchema{
  632. Name: "repeated",
  633. Repeated: true,
  634. Type: "RECORD",
  635. Schema: withTagsSchema,
  636. },
  637. &FieldSchema{
  638. Name: "anon",
  639. Repeated: true,
  640. Type: "RECORD",
  641. Schema: Schema{reqField("inside", "INTEGER")},
  642. },
  643. },
  644. },
  645. {
  646. in: withTagsEmbedded{},
  647. want: withTagsSchema,
  648. },
  649. }
  650. for i, tc := range testCases {
  651. got, err := InferSchema(tc.in)
  652. if err != nil {
  653. t.Fatalf("%d: error inferring TableSchema: %v", i, err)
  654. }
  655. if !testutil.Equal(got, tc.want) {
  656. t.Errorf("%d: inferring TableSchema: got:\n%#v\nwant:\n%#v", i,
  657. pretty.Value(got), pretty.Value(tc.want))
  658. }
  659. }
  660. }
  661. func TestTagInferenceErrors(t *testing.T) {
  662. testCases := []struct {
  663. in interface{}
  664. err error
  665. }{
  666. {
  667. in: struct {
  668. LongTag int `bigquery:"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy"`
  669. }{},
  670. err: errInvalidFieldName,
  671. },
  672. {
  673. in: struct {
  674. UnsupporedStartChar int `bigquery:"øab"`
  675. }{},
  676. err: errInvalidFieldName,
  677. },
  678. {
  679. in: struct {
  680. UnsupportedEndChar int `bigquery:"abø"`
  681. }{},
  682. err: errInvalidFieldName,
  683. },
  684. {
  685. in: struct {
  686. UnsupportedMiddleChar int `bigquery:"aøb"`
  687. }{},
  688. err: errInvalidFieldName,
  689. },
  690. {
  691. in: struct {
  692. StartInt int `bigquery:"1abc"`
  693. }{},
  694. err: errInvalidFieldName,
  695. },
  696. {
  697. in: struct {
  698. Hyphens int `bigquery:"a-b"`
  699. }{},
  700. err: errInvalidFieldName,
  701. },
  702. }
  703. for i, tc := range testCases {
  704. want := tc.err
  705. _, got := InferSchema(tc.in)
  706. if got != want {
  707. t.Errorf("%d: inferring TableSchema: got:\n%#v\nwant:\n%#v", i, got, want)
  708. }
  709. }
  710. _, err := InferSchema(struct {
  711. X int `bigquery:",optional"`
  712. }{})
  713. if err == nil {
  714. t.Error("got nil, want error")
  715. }
  716. }
  717. func TestSchemaErrors(t *testing.T) {
  718. testCases := []struct {
  719. in interface{}
  720. err error
  721. }{
  722. {
  723. in: []byte{},
  724. err: errNoStruct,
  725. },
  726. {
  727. in: new(int),
  728. err: errNoStruct,
  729. },
  730. {
  731. in: struct{ Uint uint }{},
  732. err: errUnsupportedFieldType,
  733. },
  734. {
  735. in: struct{ Uint64 uint64 }{},
  736. err: errUnsupportedFieldType,
  737. },
  738. {
  739. in: struct{ Uintptr uintptr }{},
  740. err: errUnsupportedFieldType,
  741. },
  742. {
  743. in: struct{ Complex complex64 }{},
  744. err: errUnsupportedFieldType,
  745. },
  746. {
  747. in: struct{ Map map[string]int }{},
  748. err: errUnsupportedFieldType,
  749. },
  750. {
  751. in: struct{ Chan chan bool }{},
  752. err: errUnsupportedFieldType,
  753. },
  754. {
  755. in: struct{ Ptr *int }{},
  756. err: errUnsupportedFieldType,
  757. },
  758. {
  759. in: struct{ Interface interface{} }{},
  760. err: errUnsupportedFieldType,
  761. },
  762. {
  763. in: struct{ MultiDimensional [][]int }{},
  764. err: errUnsupportedFieldType,
  765. },
  766. {
  767. in: struct{ MultiDimensional [][][]byte }{},
  768. err: errUnsupportedFieldType,
  769. },
  770. {
  771. in: struct{ SliceOfPointer []*int }{},
  772. err: errUnsupportedFieldType,
  773. },
  774. {
  775. in: struct{ SliceOfNull []NullInt64 }{},
  776. err: errUnsupportedFieldType,
  777. },
  778. {
  779. in: struct{ ChanSlice []chan bool }{},
  780. err: errUnsupportedFieldType,
  781. },
  782. {
  783. in: struct{ NestedChan struct{ Chan []chan bool } }{},
  784. err: errUnsupportedFieldType,
  785. },
  786. {
  787. in: struct {
  788. X int `bigquery:",nullable"`
  789. }{},
  790. err: errBadNullable,
  791. },
  792. {
  793. in: struct {
  794. X bool `bigquery:",nullable"`
  795. }{},
  796. err: errBadNullable,
  797. },
  798. {
  799. in: struct {
  800. X struct{ N int } `bigquery:",nullable"`
  801. }{},
  802. err: errBadNullable,
  803. },
  804. {
  805. in: struct {
  806. X []int `bigquery:",nullable"`
  807. }{},
  808. err: errBadNullable,
  809. },
  810. {
  811. in: struct{ X *[]byte }{},
  812. err: errUnsupportedFieldType,
  813. },
  814. {
  815. in: struct{ X *[]int }{},
  816. err: errUnsupportedFieldType,
  817. },
  818. {
  819. in: struct{ X *int }{},
  820. err: errUnsupportedFieldType,
  821. },
  822. }
  823. for _, tc := range testCases {
  824. want := tc.err
  825. _, got := InferSchema(tc.in)
  826. if got != want {
  827. t.Errorf("%#v: got:\n%#v\nwant:\n%#v", tc.in, got, want)
  828. }
  829. }
  830. }
  831. func TestHasRecursiveType(t *testing.T) {
  832. type (
  833. nonStruct int
  834. nonRec struct{ A string }
  835. dup struct{ A, B nonRec }
  836. rec struct {
  837. A int
  838. B *rec
  839. }
  840. recUnexported struct {
  841. A int
  842. b *rec
  843. }
  844. hasRec struct {
  845. A int
  846. R *rec
  847. }
  848. recSlicePointer struct {
  849. A []*recSlicePointer
  850. }
  851. )
  852. for _, test := range []struct {
  853. in interface{}
  854. want bool
  855. }{
  856. {nonStruct(0), false},
  857. {nonRec{}, false},
  858. {dup{}, false},
  859. {rec{}, true},
  860. {recUnexported{}, false},
  861. {hasRec{}, true},
  862. {&recSlicePointer{}, true},
  863. } {
  864. got, err := hasRecursiveType(reflect.TypeOf(test.in), nil)
  865. if err != nil {
  866. t.Fatal(err)
  867. }
  868. if got != test.want {
  869. t.Errorf("%T: got %t, want %t", test.in, got, test.want)
  870. }
  871. }
  872. }