schema_test.go 23 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052
  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. NotNested int
  333. Nested struct {
  334. Inside int
  335. }
  336. }
  337. type containsDoubleNested struct {
  338. NotNested int
  339. Nested struct {
  340. InsideNested struct {
  341. Inside int
  342. }
  343. }
  344. }
  345. type ptrNested struct {
  346. Ptr *struct{ Inside int }
  347. }
  348. type dup struct { // more than one field of the same struct type
  349. A, B allBoolean
  350. }
  351. func TestNestedInference(t *testing.T) {
  352. testCases := []struct {
  353. in interface{}
  354. want Schema
  355. }{
  356. {
  357. in: containsNested{},
  358. want: Schema{
  359. reqField("NotNested", "INTEGER"),
  360. &FieldSchema{
  361. Name: "Nested",
  362. Required: true,
  363. Type: "RECORD",
  364. Schema: Schema{reqField("Inside", "INTEGER")},
  365. },
  366. },
  367. },
  368. {
  369. in: containsDoubleNested{},
  370. want: Schema{
  371. reqField("NotNested", "INTEGER"),
  372. &FieldSchema{
  373. Name: "Nested",
  374. Required: true,
  375. Type: "RECORD",
  376. Schema: Schema{
  377. {
  378. Name: "InsideNested",
  379. Required: true,
  380. Type: "RECORD",
  381. Schema: Schema{reqField("Inside", "INTEGER")},
  382. },
  383. },
  384. },
  385. },
  386. },
  387. {
  388. in: ptrNested{},
  389. want: Schema{
  390. &FieldSchema{
  391. Name: "Ptr",
  392. Required: true,
  393. Type: "RECORD",
  394. Schema: Schema{reqField("Inside", "INTEGER")},
  395. },
  396. },
  397. },
  398. {
  399. in: dup{},
  400. want: Schema{
  401. &FieldSchema{
  402. Name: "A",
  403. Required: true,
  404. Type: "RECORD",
  405. Schema: Schema{reqField("Bool", "BOOLEAN")},
  406. },
  407. &FieldSchema{
  408. Name: "B",
  409. Required: true,
  410. Type: "RECORD",
  411. Schema: Schema{reqField("Bool", "BOOLEAN")},
  412. },
  413. },
  414. },
  415. }
  416. for _, tc := range testCases {
  417. got, err := InferSchema(tc.in)
  418. if err != nil {
  419. t.Fatalf("%T: error inferring TableSchema: %v", tc.in, err)
  420. }
  421. if !testutil.Equal(got, tc.want) {
  422. t.Errorf("%T: inferring TableSchema: got:\n%#v\nwant:\n%#v", tc.in,
  423. pretty.Value(got), pretty.Value(tc.want))
  424. }
  425. }
  426. }
  427. type repeated struct {
  428. NotRepeated []byte
  429. RepeatedByteSlice [][]byte
  430. Slice []int
  431. Array [5]bool
  432. }
  433. type nestedRepeated struct {
  434. NotRepeated int
  435. Repeated []struct {
  436. Inside int
  437. }
  438. RepeatedPtr []*struct{ Inside int }
  439. }
  440. func repField(name, typ string) *FieldSchema {
  441. return &FieldSchema{
  442. Name: name,
  443. Type: FieldType(typ),
  444. Repeated: true,
  445. }
  446. }
  447. func TestRepeatedInference(t *testing.T) {
  448. testCases := []struct {
  449. in interface{}
  450. want Schema
  451. }{
  452. {
  453. in: repeated{},
  454. want: Schema{
  455. reqField("NotRepeated", "BYTES"),
  456. repField("RepeatedByteSlice", "BYTES"),
  457. repField("Slice", "INTEGER"),
  458. repField("Array", "BOOLEAN"),
  459. },
  460. },
  461. {
  462. in: nestedRepeated{},
  463. want: Schema{
  464. reqField("NotRepeated", "INTEGER"),
  465. {
  466. Name: "Repeated",
  467. Repeated: true,
  468. Type: "RECORD",
  469. Schema: Schema{reqField("Inside", "INTEGER")},
  470. },
  471. {
  472. Name: "RepeatedPtr",
  473. Repeated: true,
  474. Type: "RECORD",
  475. Schema: Schema{reqField("Inside", "INTEGER")},
  476. },
  477. },
  478. },
  479. }
  480. for i, tc := range testCases {
  481. got, err := InferSchema(tc.in)
  482. if err != nil {
  483. t.Fatalf("%d: error inferring TableSchema: %v", i, err)
  484. }
  485. if !testutil.Equal(got, tc.want) {
  486. t.Errorf("%d: inferring TableSchema: got:\n%#v\nwant:\n%#v", i,
  487. pretty.Value(got), pretty.Value(tc.want))
  488. }
  489. }
  490. }
  491. type allNulls struct {
  492. A NullInt64
  493. B NullFloat64
  494. C NullBool
  495. D NullString
  496. E NullTimestamp
  497. F NullTime
  498. G NullDate
  499. H NullDateTime
  500. }
  501. func TestNullInference(t *testing.T) {
  502. got, err := InferSchema(allNulls{})
  503. if err != nil {
  504. t.Fatal(err)
  505. }
  506. want := Schema{
  507. optField("A", "INTEGER"),
  508. optField("B", "FLOAT"),
  509. optField("C", "BOOLEAN"),
  510. optField("D", "STRING"),
  511. optField("E", "TIMESTAMP"),
  512. optField("F", "TIME"),
  513. optField("G", "DATE"),
  514. optField("H", "DATETIME"),
  515. }
  516. if diff := testutil.Diff(got, want); diff != "" {
  517. t.Error(diff)
  518. }
  519. }
  520. type Embedded struct {
  521. Embedded int
  522. }
  523. type embedded struct {
  524. Embedded2 int
  525. }
  526. type nestedEmbedded struct {
  527. Embedded
  528. embedded
  529. }
  530. func TestEmbeddedInference(t *testing.T) {
  531. got, err := InferSchema(nestedEmbedded{})
  532. if err != nil {
  533. t.Fatal(err)
  534. }
  535. want := Schema{
  536. reqField("Embedded", "INTEGER"),
  537. reqField("Embedded2", "INTEGER"),
  538. }
  539. if !testutil.Equal(got, want) {
  540. t.Errorf("got %v, want %v", pretty.Value(got), pretty.Value(want))
  541. }
  542. }
  543. func TestRecursiveInference(t *testing.T) {
  544. type List struct {
  545. Val int
  546. Next *List
  547. }
  548. _, err := InferSchema(List{})
  549. if err == nil {
  550. t.Fatal("got nil, want error")
  551. }
  552. }
  553. type withTags struct {
  554. NoTag int
  555. ExcludeTag int `bigquery:"-"`
  556. SimpleTag int `bigquery:"simple_tag"`
  557. UnderscoreTag int `bigquery:"_id"`
  558. MixedCase int `bigquery:"MIXEDcase"`
  559. Nullable []byte `bigquery:",nullable"`
  560. NullNumeric *big.Rat `bigquery:",nullable"`
  561. }
  562. type withTagsNested struct {
  563. Nested withTags `bigquery:"nested"`
  564. NestedAnonymous struct {
  565. ExcludeTag int `bigquery:"-"`
  566. Inside int `bigquery:"inside"`
  567. } `bigquery:"anon"`
  568. PNested *struct{ X int } // not nullable, for backwards compatibility
  569. PNestedNullable *struct{ X int } `bigquery:",nullable"`
  570. }
  571. type withTagsRepeated struct {
  572. Repeated []withTags `bigquery:"repeated"`
  573. RepeatedAnonymous []struct {
  574. ExcludeTag int `bigquery:"-"`
  575. Inside int `bigquery:"inside"`
  576. } `bigquery:"anon"`
  577. }
  578. type withTagsEmbedded struct {
  579. withTags
  580. }
  581. var withTagsSchema = Schema{
  582. reqField("NoTag", "INTEGER"),
  583. reqField("simple_tag", "INTEGER"),
  584. reqField("_id", "INTEGER"),
  585. reqField("MIXEDcase", "INTEGER"),
  586. optField("Nullable", "BYTES"),
  587. optField("NullNumeric", "NUMERIC"),
  588. }
  589. func TestTagInference(t *testing.T) {
  590. testCases := []struct {
  591. in interface{}
  592. want Schema
  593. }{
  594. {
  595. in: withTags{},
  596. want: withTagsSchema,
  597. },
  598. {
  599. in: withTagsNested{},
  600. want: Schema{
  601. &FieldSchema{
  602. Name: "nested",
  603. Required: true,
  604. Type: "RECORD",
  605. Schema: withTagsSchema,
  606. },
  607. &FieldSchema{
  608. Name: "anon",
  609. Required: true,
  610. Type: "RECORD",
  611. Schema: Schema{reqField("inside", "INTEGER")},
  612. },
  613. &FieldSchema{
  614. Name: "PNested",
  615. Required: true,
  616. Type: "RECORD",
  617. Schema: Schema{reqField("X", "INTEGER")},
  618. },
  619. &FieldSchema{
  620. Name: "PNestedNullable",
  621. Required: false,
  622. Type: "RECORD",
  623. Schema: Schema{reqField("X", "INTEGER")},
  624. },
  625. },
  626. },
  627. {
  628. in: withTagsRepeated{},
  629. want: Schema{
  630. &FieldSchema{
  631. Name: "repeated",
  632. Repeated: true,
  633. Type: "RECORD",
  634. Schema: withTagsSchema,
  635. },
  636. &FieldSchema{
  637. Name: "anon",
  638. Repeated: true,
  639. Type: "RECORD",
  640. Schema: Schema{reqField("inside", "INTEGER")},
  641. },
  642. },
  643. },
  644. {
  645. in: withTagsEmbedded{},
  646. want: withTagsSchema,
  647. },
  648. }
  649. for i, tc := range testCases {
  650. got, err := InferSchema(tc.in)
  651. if err != nil {
  652. t.Fatalf("%d: error inferring TableSchema: %v", i, err)
  653. }
  654. if !testutil.Equal(got, tc.want) {
  655. t.Errorf("%d: inferring TableSchema: got:\n%#v\nwant:\n%#v", i,
  656. pretty.Value(got), pretty.Value(tc.want))
  657. }
  658. }
  659. }
  660. func TestTagInferenceErrors(t *testing.T) {
  661. testCases := []struct {
  662. in interface{}
  663. err error
  664. }{
  665. {
  666. in: struct {
  667. LongTag int `bigquery:"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy"`
  668. }{},
  669. err: errInvalidFieldName,
  670. },
  671. {
  672. in: struct {
  673. UnsupporedStartChar int `bigquery:"øab"`
  674. }{},
  675. err: errInvalidFieldName,
  676. },
  677. {
  678. in: struct {
  679. UnsupportedEndChar int `bigquery:"abø"`
  680. }{},
  681. err: errInvalidFieldName,
  682. },
  683. {
  684. in: struct {
  685. UnsupportedMiddleChar int `bigquery:"aøb"`
  686. }{},
  687. err: errInvalidFieldName,
  688. },
  689. {
  690. in: struct {
  691. StartInt int `bigquery:"1abc"`
  692. }{},
  693. err: errInvalidFieldName,
  694. },
  695. {
  696. in: struct {
  697. Hyphens int `bigquery:"a-b"`
  698. }{},
  699. err: errInvalidFieldName,
  700. },
  701. }
  702. for i, tc := range testCases {
  703. want := tc.err
  704. _, got := InferSchema(tc.in)
  705. if got != want {
  706. t.Errorf("%d: inferring TableSchema: got:\n%#v\nwant:\n%#v", i, got, want)
  707. }
  708. }
  709. _, err := InferSchema(struct {
  710. X int `bigquery:",optional"`
  711. }{})
  712. if err == nil {
  713. t.Error("got nil, want error")
  714. }
  715. }
  716. func TestSchemaErrors(t *testing.T) {
  717. testCases := []struct {
  718. in interface{}
  719. err error
  720. }{
  721. {
  722. in: []byte{},
  723. err: errNoStruct,
  724. },
  725. {
  726. in: new(int),
  727. err: errNoStruct,
  728. },
  729. {
  730. in: struct{ Uint uint }{},
  731. err: errUnsupportedFieldType,
  732. },
  733. {
  734. in: struct{ Uint64 uint64 }{},
  735. err: errUnsupportedFieldType,
  736. },
  737. {
  738. in: struct{ Uintptr uintptr }{},
  739. err: errUnsupportedFieldType,
  740. },
  741. {
  742. in: struct{ Complex complex64 }{},
  743. err: errUnsupportedFieldType,
  744. },
  745. {
  746. in: struct{ Map map[string]int }{},
  747. err: errUnsupportedFieldType,
  748. },
  749. {
  750. in: struct{ Chan chan bool }{},
  751. err: errUnsupportedFieldType,
  752. },
  753. {
  754. in: struct{ Ptr *int }{},
  755. err: errUnsupportedFieldType,
  756. },
  757. {
  758. in: struct{ Interface interface{} }{},
  759. err: errUnsupportedFieldType,
  760. },
  761. {
  762. in: struct{ MultiDimensional [][]int }{},
  763. err: errUnsupportedFieldType,
  764. },
  765. {
  766. in: struct{ MultiDimensional [][][]byte }{},
  767. err: errUnsupportedFieldType,
  768. },
  769. {
  770. in: struct{ SliceOfPointer []*int }{},
  771. err: errUnsupportedFieldType,
  772. },
  773. {
  774. in: struct{ SliceOfNull []NullInt64 }{},
  775. err: errUnsupportedFieldType,
  776. },
  777. {
  778. in: struct{ ChanSlice []chan bool }{},
  779. err: errUnsupportedFieldType,
  780. },
  781. {
  782. in: struct{ NestedChan struct{ Chan []chan bool } }{},
  783. err: errUnsupportedFieldType,
  784. },
  785. {
  786. in: struct {
  787. X int `bigquery:",nullable"`
  788. }{},
  789. err: errBadNullable,
  790. },
  791. {
  792. in: struct {
  793. X bool `bigquery:",nullable"`
  794. }{},
  795. err: errBadNullable,
  796. },
  797. {
  798. in: struct {
  799. X struct{ N int } `bigquery:",nullable"`
  800. }{},
  801. err: errBadNullable,
  802. },
  803. {
  804. in: struct {
  805. X []int `bigquery:",nullable"`
  806. }{},
  807. err: errBadNullable,
  808. },
  809. {
  810. in: struct{ X *[]byte }{},
  811. err: errUnsupportedFieldType,
  812. },
  813. {
  814. in: struct{ X *[]int }{},
  815. err: errUnsupportedFieldType,
  816. },
  817. {
  818. in: struct{ X *int }{},
  819. err: errUnsupportedFieldType,
  820. },
  821. }
  822. for _, tc := range testCases {
  823. want := tc.err
  824. _, got := InferSchema(tc.in)
  825. if got != want {
  826. t.Errorf("%#v: got:\n%#v\nwant:\n%#v", tc.in, got, want)
  827. }
  828. }
  829. }
  830. func TestHasRecursiveType(t *testing.T) {
  831. type (
  832. nonStruct int
  833. nonRec struct{ A string }
  834. dup struct{ A, B nonRec }
  835. rec struct {
  836. A int
  837. B *rec
  838. }
  839. recUnexported struct {
  840. A int
  841. }
  842. hasRec struct {
  843. A int
  844. R *rec
  845. }
  846. recSlicePointer struct {
  847. A []*recSlicePointer
  848. }
  849. )
  850. for _, test := range []struct {
  851. in interface{}
  852. want bool
  853. }{
  854. {nonStruct(0), false},
  855. {nonRec{}, false},
  856. {dup{}, false},
  857. {rec{}, true},
  858. {recUnexported{}, false},
  859. {hasRec{}, true},
  860. {&recSlicePointer{}, true},
  861. } {
  862. got, err := hasRecursiveType(reflect.TypeOf(test.in), nil)
  863. if err != nil {
  864. t.Fatal(err)
  865. }
  866. if got != test.want {
  867. t.Errorf("%T: got %t, want %t", test.in, got, test.want)
  868. }
  869. }
  870. }
  871. func TestSchemaFromJSON(t *testing.T) {
  872. testCasesExpectingSuccess := []struct {
  873. bqSchemaJSON []byte
  874. description string
  875. expectedSchema Schema
  876. }{
  877. {
  878. description: "Flat table with a mixture of NULLABLE and REQUIRED fields",
  879. bqSchemaJSON: []byte(`
  880. [
  881. {"name":"flat_string","type":"STRING","mode":"NULLABLE","description":"Flat nullable string"},
  882. {"name":"flat_bytes","type":"BYTES","mode":"REQUIRED","description":"Flat required BYTES"},
  883. {"name":"flat_integer","type":"INTEGER","mode":"NULLABLE","description":"Flat nullable INTEGER"},
  884. {"name":"flat_float","type":"FLOAT","mode":"REQUIRED","description":"Flat required FLOAT"},
  885. {"name":"flat_boolean","type":"BOOLEAN","mode":"NULLABLE","description":"Flat nullable BOOLEAN"},
  886. {"name":"flat_timestamp","type":"TIMESTAMP","mode":"REQUIRED","description":"Flat required TIMESTAMP"},
  887. {"name":"flat_date","type":"DATE","mode":"NULLABLE","description":"Flat required DATE"},
  888. {"name":"flat_time","type":"TIME","mode":"REQUIRED","description":"Flat nullable TIME"},
  889. {"name":"flat_datetime","type":"DATETIME","mode":"NULLABLE","description":"Flat required DATETIME"},
  890. {"name":"flat_numeric","type":"NUMERIC","mode":"REQUIRED","description":"Flat nullable NUMERIC"}
  891. ]`),
  892. expectedSchema: Schema{
  893. fieldSchema("Flat nullable string", "flat_string", "STRING", false, false),
  894. fieldSchema("Flat required BYTES", "flat_bytes", "BYTES", false, true),
  895. fieldSchema("Flat nullable INTEGER", "flat_integer", "INTEGER", false, false),
  896. fieldSchema("Flat required FLOAT", "flat_float", "FLOAT", false, true),
  897. fieldSchema("Flat nullable BOOLEAN", "flat_boolean", "BOOLEAN", false, false),
  898. fieldSchema("Flat required TIMESTAMP", "flat_timestamp", "TIMESTAMP", false, true),
  899. fieldSchema("Flat required DATE", "flat_date", "DATE", false, false),
  900. fieldSchema("Flat nullable TIME", "flat_time", "TIME", false, true),
  901. fieldSchema("Flat required DATETIME", "flat_datetime", "DATETIME", false, false),
  902. fieldSchema("Flat nullable NUMERIC", "flat_numeric", "NUMERIC", false, true),
  903. },
  904. },
  905. {
  906. description: "Table with a nested RECORD",
  907. bqSchemaJSON: []byte(`
  908. [
  909. {"name":"flat_string","type":"STRING","mode":"NULLABLE","description":"Flat nullable string"},
  910. {"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"}]}
  911. ]`),
  912. expectedSchema: Schema{
  913. fieldSchema("Flat nullable string", "flat_string", "STRING", false, false),
  914. &FieldSchema{
  915. Description: "Nested nullable RECORD",
  916. Name: "nested_record",
  917. Required: false,
  918. Type: "RECORD",
  919. Schema: Schema{
  920. {
  921. Description: "First nested record field",
  922. Name: "record_field_1",
  923. Required: false,
  924. Type: "STRING",
  925. },
  926. {
  927. Description: "Second nested record field",
  928. Name: "record_field_2",
  929. Required: true,
  930. Type: "INTEGER",
  931. },
  932. },
  933. },
  934. },
  935. },
  936. {
  937. description: "Table with a repeated RECORD",
  938. bqSchemaJSON: []byte(`
  939. [
  940. {"name":"flat_string","type":"STRING","mode":"NULLABLE","description":"Flat nullable string"},
  941. {"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"}]}
  942. ]`),
  943. expectedSchema: Schema{
  944. fieldSchema("Flat nullable string", "flat_string", "STRING", false, false),
  945. &FieldSchema{
  946. Description: "Nested nullable RECORD",
  947. Name: "nested_record",
  948. Repeated: true,
  949. Required: false,
  950. Type: "RECORD",
  951. Schema: Schema{
  952. {
  953. Description: "First nested record field",
  954. Name: "record_field_1",
  955. Required: false,
  956. Type: "STRING",
  957. },
  958. {
  959. Description: "Second nested record field",
  960. Name: "record_field_2",
  961. Required: true,
  962. Type: "INTEGER",
  963. },
  964. },
  965. },
  966. },
  967. },
  968. }
  969. for _, tc := range testCasesExpectingSuccess {
  970. convertedSchema, err := SchemaFromJSON(tc.bqSchemaJSON)
  971. if err != nil {
  972. t.Errorf("encountered an error when converting JSON table schema (%s): %v", tc.description, err)
  973. continue
  974. }
  975. if !testutil.Equal(convertedSchema, tc.expectedSchema) {
  976. t.Errorf("generated JSON table schema (%s) differs from the expected schema", tc.description)
  977. }
  978. }
  979. testCasesExpectingFailure := []struct {
  980. bqSchemaJSON []byte
  981. description string
  982. }{
  983. {
  984. description: "Schema with invalid JSON",
  985. bqSchemaJSON: []byte(`This is not JSON`),
  986. },
  987. {
  988. description: "Schema with unknown field type",
  989. bqSchemaJSON: []byte(`[{"name":"strange_type","type":"STRANGE","description":"This type should not exist"}]`),
  990. },
  991. {
  992. description: "Schema with zero length",
  993. bqSchemaJSON: []byte(``),
  994. },
  995. }
  996. for _, tc := range testCasesExpectingFailure {
  997. _, err := SchemaFromJSON(tc.bqSchemaJSON)
  998. if err == nil {
  999. t.Errorf("converting this schema should have returned an error (%s): %v", tc.description, err)
  1000. continue
  1001. }
  1002. }
  1003. }