table_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. // Copyright 2017 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. "testing"
  17. "time"
  18. "cloud.google.com/go/internal/testutil"
  19. bq "google.golang.org/api/bigquery/v2"
  20. )
  21. func TestBQToTableMetadata(t *testing.T) {
  22. aTime := time.Date(2017, 1, 26, 0, 0, 0, 0, time.Local)
  23. aTimeMillis := aTime.UnixNano() / 1e6
  24. for _, test := range []struct {
  25. in *bq.Table
  26. want *TableMetadata
  27. }{
  28. {&bq.Table{}, &TableMetadata{}}, // test minimal case
  29. {
  30. &bq.Table{
  31. CreationTime: aTimeMillis,
  32. Description: "desc",
  33. Etag: "etag",
  34. ExpirationTime: aTimeMillis,
  35. FriendlyName: "fname",
  36. Id: "id",
  37. LastModifiedTime: uint64(aTimeMillis),
  38. Location: "loc",
  39. NumBytes: 123,
  40. NumLongTermBytes: 23,
  41. NumRows: 7,
  42. StreamingBuffer: &bq.Streamingbuffer{
  43. EstimatedBytes: 11,
  44. EstimatedRows: 3,
  45. OldestEntryTime: uint64(aTimeMillis),
  46. },
  47. TimePartitioning: &bq.TimePartitioning{
  48. ExpirationMs: 7890,
  49. Type: "DAY",
  50. Field: "pfield",
  51. },
  52. Clustering: &bq.Clustering{
  53. Fields: []string{"cfield1", "cfield2"},
  54. },
  55. EncryptionConfiguration: &bq.EncryptionConfiguration{KmsKeyName: "keyName"},
  56. Type: "EXTERNAL",
  57. View: &bq.ViewDefinition{Query: "view-query"},
  58. Labels: map[string]string{"a": "b"},
  59. ExternalDataConfiguration: &bq.ExternalDataConfiguration{
  60. SourceFormat: "GOOGLE_SHEETS",
  61. },
  62. },
  63. &TableMetadata{
  64. Description: "desc",
  65. Name: "fname",
  66. ViewQuery: "view-query",
  67. FullID: "id",
  68. Type: ExternalTable,
  69. Labels: map[string]string{"a": "b"},
  70. ExternalDataConfig: &ExternalDataConfig{SourceFormat: GoogleSheets},
  71. ExpirationTime: aTime.Truncate(time.Millisecond),
  72. CreationTime: aTime.Truncate(time.Millisecond),
  73. LastModifiedTime: aTime.Truncate(time.Millisecond),
  74. NumBytes: 123,
  75. NumRows: 7,
  76. TimePartitioning: &TimePartitioning{
  77. Expiration: 7890 * time.Millisecond,
  78. Field: "pfield",
  79. },
  80. Clustering: &Clustering{
  81. Fields: []string{"cfield1", "cfield2"},
  82. },
  83. StreamingBuffer: &StreamingBuffer{
  84. EstimatedBytes: 11,
  85. EstimatedRows: 3,
  86. OldestEntryTime: aTime,
  87. },
  88. EncryptionConfig: &EncryptionConfig{KMSKeyName: "keyName"},
  89. ETag: "etag",
  90. },
  91. },
  92. } {
  93. got, err := bqToTableMetadata(test.in)
  94. if err != nil {
  95. t.Fatal(err)
  96. }
  97. if diff := testutil.Diff(got, test.want); diff != "" {
  98. t.Errorf("%+v:\n, -got, +want:\n%s", test.in, diff)
  99. }
  100. }
  101. }
  102. func TestTableMetadataToBQ(t *testing.T) {
  103. aTime := time.Date(2017, 1, 26, 0, 0, 0, 0, time.Local)
  104. aTimeMillis := aTime.UnixNano() / 1e6
  105. sc := Schema{fieldSchema("desc", "name", "STRING", false, true)}
  106. for _, test := range []struct {
  107. in *TableMetadata
  108. want *bq.Table
  109. }{
  110. {nil, &bq.Table{}},
  111. {&TableMetadata{}, &bq.Table{}},
  112. {
  113. &TableMetadata{
  114. Name: "n",
  115. Description: "d",
  116. Schema: sc,
  117. ExpirationTime: aTime,
  118. Labels: map[string]string{"a": "b"},
  119. ExternalDataConfig: &ExternalDataConfig{SourceFormat: Bigtable},
  120. EncryptionConfig: &EncryptionConfig{KMSKeyName: "keyName"},
  121. },
  122. &bq.Table{
  123. FriendlyName: "n",
  124. Description: "d",
  125. Schema: &bq.TableSchema{
  126. Fields: []*bq.TableFieldSchema{
  127. bqTableFieldSchema("desc", "name", "STRING", "REQUIRED"),
  128. },
  129. },
  130. ExpirationTime: aTimeMillis,
  131. Labels: map[string]string{"a": "b"},
  132. ExternalDataConfiguration: &bq.ExternalDataConfiguration{SourceFormat: "BIGTABLE"},
  133. EncryptionConfiguration: &bq.EncryptionConfiguration{KmsKeyName: "keyName"},
  134. },
  135. },
  136. {
  137. &TableMetadata{ViewQuery: "q"},
  138. &bq.Table{
  139. View: &bq.ViewDefinition{
  140. Query: "q",
  141. UseLegacySql: false,
  142. ForceSendFields: []string{"UseLegacySql"},
  143. },
  144. },
  145. },
  146. {
  147. &TableMetadata{
  148. ViewQuery: "q",
  149. UseLegacySQL: true,
  150. TimePartitioning: &TimePartitioning{},
  151. },
  152. &bq.Table{
  153. View: &bq.ViewDefinition{
  154. Query: "q",
  155. UseLegacySql: true,
  156. },
  157. TimePartitioning: &bq.TimePartitioning{
  158. Type: "DAY",
  159. ExpirationMs: 0,
  160. },
  161. },
  162. },
  163. {
  164. &TableMetadata{
  165. ViewQuery: "q",
  166. UseStandardSQL: true,
  167. TimePartitioning: &TimePartitioning{
  168. Expiration: time.Second,
  169. Field: "ofDreams",
  170. },
  171. Clustering: &Clustering{
  172. Fields: []string{"cfield1"},
  173. },
  174. },
  175. &bq.Table{
  176. View: &bq.ViewDefinition{
  177. Query: "q",
  178. UseLegacySql: false,
  179. ForceSendFields: []string{"UseLegacySql"},
  180. },
  181. TimePartitioning: &bq.TimePartitioning{
  182. Type: "DAY",
  183. ExpirationMs: 1000,
  184. Field: "ofDreams",
  185. },
  186. Clustering: &bq.Clustering{
  187. Fields: []string{"cfield1"},
  188. },
  189. },
  190. },
  191. {
  192. &TableMetadata{ExpirationTime: NeverExpire},
  193. &bq.Table{ExpirationTime: 0},
  194. },
  195. } {
  196. got, err := test.in.toBQ()
  197. if err != nil {
  198. t.Fatalf("%+v: %v", test.in, err)
  199. }
  200. if diff := testutil.Diff(got, test.want); diff != "" {
  201. t.Errorf("%+v:\n-got, +want:\n%s", test.in, diff)
  202. }
  203. }
  204. // Errors
  205. for _, in := range []*TableMetadata{
  206. {Schema: sc, ViewQuery: "q"}, // can't have both schema and query
  207. {UseLegacySQL: true}, // UseLegacySQL without query
  208. {UseStandardSQL: true}, // UseStandardSQL without query
  209. // read-only fields
  210. {FullID: "x"},
  211. {Type: "x"},
  212. {CreationTime: aTime},
  213. {LastModifiedTime: aTime},
  214. {NumBytes: 1},
  215. {NumRows: 1},
  216. {StreamingBuffer: &StreamingBuffer{}},
  217. {ETag: "x"},
  218. // expiration time outside allowable range is invalid
  219. // See https://godoc.org/time#Time.UnixNano
  220. {ExpirationTime: time.Date(1677, 9, 21, 0, 12, 43, 145224192, time.UTC).Add(-1)},
  221. {ExpirationTime: time.Date(2262, 04, 11, 23, 47, 16, 854775807, time.UTC).Add(1)},
  222. } {
  223. _, err := in.toBQ()
  224. if err == nil {
  225. t.Errorf("%+v: got nil, want error", in)
  226. }
  227. }
  228. }
  229. func TestTableMetadataToUpdateToBQ(t *testing.T) {
  230. aTime := time.Date(2017, 1, 26, 0, 0, 0, 0, time.Local)
  231. for _, test := range []struct {
  232. tm TableMetadataToUpdate
  233. want *bq.Table
  234. }{
  235. {
  236. tm: TableMetadataToUpdate{},
  237. want: &bq.Table{},
  238. },
  239. {
  240. tm: TableMetadataToUpdate{
  241. Description: "d",
  242. Name: "n",
  243. },
  244. want: &bq.Table{
  245. Description: "d",
  246. FriendlyName: "n",
  247. ForceSendFields: []string{"Description", "FriendlyName"},
  248. },
  249. },
  250. {
  251. tm: TableMetadataToUpdate{
  252. Schema: Schema{fieldSchema("desc", "name", "STRING", false, true)},
  253. ExpirationTime: aTime,
  254. },
  255. want: &bq.Table{
  256. Schema: &bq.TableSchema{
  257. Fields: []*bq.TableFieldSchema{
  258. bqTableFieldSchema("desc", "name", "STRING", "REQUIRED"),
  259. },
  260. },
  261. ExpirationTime: aTime.UnixNano() / 1e6,
  262. ForceSendFields: []string{"Schema", "ExpirationTime"},
  263. },
  264. },
  265. {
  266. tm: TableMetadataToUpdate{ViewQuery: "q"},
  267. want: &bq.Table{
  268. View: &bq.ViewDefinition{Query: "q", ForceSendFields: []string{"Query"}},
  269. },
  270. },
  271. {
  272. tm: TableMetadataToUpdate{UseLegacySQL: false},
  273. want: &bq.Table{
  274. View: &bq.ViewDefinition{
  275. UseLegacySql: false,
  276. ForceSendFields: []string{"UseLegacySql"},
  277. },
  278. },
  279. },
  280. {
  281. tm: TableMetadataToUpdate{ViewQuery: "q", UseLegacySQL: true},
  282. want: &bq.Table{
  283. View: &bq.ViewDefinition{
  284. Query: "q",
  285. UseLegacySql: true,
  286. ForceSendFields: []string{"Query", "UseLegacySql"},
  287. },
  288. },
  289. },
  290. {
  291. tm: func() (tm TableMetadataToUpdate) {
  292. tm.SetLabel("L", "V")
  293. tm.DeleteLabel("D")
  294. return tm
  295. }(),
  296. want: &bq.Table{
  297. Labels: map[string]string{"L": "V"},
  298. NullFields: []string{"Labels.D"},
  299. },
  300. },
  301. {
  302. tm: TableMetadataToUpdate{ExpirationTime: NeverExpire},
  303. want: &bq.Table{
  304. NullFields: []string{"ExpirationTime"},
  305. },
  306. },
  307. {
  308. tm: TableMetadataToUpdate{TimePartitioning: &TimePartitioning{Expiration: 0}},
  309. want: &bq.Table{
  310. TimePartitioning: &bq.TimePartitioning{
  311. Type: "DAY",
  312. ForceSendFields: []string{"RequirePartitionFilter"},
  313. NullFields: []string{"ExpirationMs"},
  314. },
  315. },
  316. },
  317. {
  318. tm: TableMetadataToUpdate{TimePartitioning: &TimePartitioning{Expiration: time.Duration(time.Hour)}},
  319. want: &bq.Table{
  320. TimePartitioning: &bq.TimePartitioning{
  321. ExpirationMs: 3600000,
  322. Type: "DAY",
  323. ForceSendFields: []string{"RequirePartitionFilter"},
  324. },
  325. },
  326. },
  327. } {
  328. got, _ := test.tm.toBQ()
  329. if !testutil.Equal(got, test.want) {
  330. t.Errorf("%+v:\ngot %+v\nwant %+v", test.tm, got, test.want)
  331. }
  332. }
  333. }
  334. func TestTableMetadataToUpdateToBQErrors(t *testing.T) {
  335. // See https://godoc.org/time#Time.UnixNano
  336. start := time.Date(1677, 9, 21, 0, 12, 43, 145224192, time.UTC)
  337. end := time.Date(2262, 04, 11, 23, 47, 16, 854775807, time.UTC)
  338. for _, test := range []struct {
  339. desc string
  340. aTime time.Time
  341. wantErr bool
  342. }{
  343. {desc: "ignored zero value", aTime: time.Time{}, wantErr: false},
  344. {desc: "earliest valid time", aTime: start, wantErr: false},
  345. {desc: "latested valid time", aTime: end, wantErr: false},
  346. {desc: "invalid times before 1678", aTime: start.Add(-1), wantErr: true},
  347. {desc: "invalid times after 2262", aTime: end.Add(1), wantErr: true},
  348. {desc: "valid times after 1678", aTime: start.Add(1), wantErr: false},
  349. {desc: "valid times before 2262", aTime: end.Add(-1), wantErr: false},
  350. } {
  351. tm := &TableMetadataToUpdate{ExpirationTime: test.aTime}
  352. _, err := tm.toBQ()
  353. if test.wantErr && err == nil {
  354. t.Errorf("[%s] got no error, want error", test.desc)
  355. }
  356. if !test.wantErr && err != nil {
  357. t.Errorf("[%s] got error, want no error", test.desc)
  358. }
  359. }
  360. }