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.
 
 
 

373 lines
10 KiB

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