@@ -7,6 +7,7 @@ use bytes::{BufMut, Bytes, BytesMut};
7
7
use chrono:: { DateTime , TimeZone , Utc } ;
8
8
use database:: pool:: { postgres, sqlite, ConnectionManager } ;
9
9
use futures_util:: sink:: SinkExt ;
10
+ use hashbrown:: HashMap ;
10
11
use serde:: { Serialize , Serializer } ;
11
12
use std:: convert:: { TryFrom , TryInto } ;
12
13
use std:: io:: Write ;
@@ -21,6 +22,9 @@ trait Table {
21
22
/// Comma-separated list of table's attribute names in SQLite.
22
23
fn sqlite_attributes ( ) -> & ' static str ;
23
24
25
+ /// Comma-separated list of table's attribute names in Postgres.
26
+ fn postgres_attributes ( ) -> & ' static str ;
27
+
24
28
/// Name of `generated always as identity` attribute in Postgres,
25
29
/// if applicable.
26
30
fn postgres_generated_id_attribute ( ) -> Option < & ' static str > ;
@@ -56,6 +60,10 @@ impl Table for Artifact {
56
60
"id, name, date, type"
57
61
}
58
62
63
+ fn postgres_attributes ( ) -> & ' static str {
64
+ "id, name, date, type"
65
+ }
66
+
59
67
fn postgres_generated_id_attribute ( ) -> Option < & ' static str > {
60
68
Some ( "id" )
61
69
}
@@ -92,6 +100,10 @@ impl Table for ArtifactCollectionDuration {
92
100
"aid, date_recorded, duration"
93
101
}
94
102
103
+ fn postgres_attributes ( ) -> & ' static str {
104
+ "aid, date_recorded, duration"
105
+ }
106
+
95
107
fn postgres_generated_id_attribute ( ) -> Option < & ' static str > {
96
108
None
97
109
}
@@ -128,6 +140,10 @@ impl Table for Benchmark {
128
140
"name, stabilized, category"
129
141
}
130
142
143
+ fn postgres_attributes ( ) -> & ' static str {
144
+ "name, stabilized, category"
145
+ }
146
+
131
147
fn postgres_generated_id_attribute ( ) -> Option < & ' static str > {
132
148
None
133
149
}
@@ -160,6 +176,10 @@ impl Table for Collection {
160
176
"id, perf_commit"
161
177
}
162
178
179
+ fn postgres_attributes ( ) -> & ' static str {
180
+ "id, perf_commit"
181
+ }
182
+
163
183
fn postgres_generated_id_attribute ( ) -> Option < & ' static str > {
164
184
Some ( "id" )
165
185
}
@@ -193,6 +213,10 @@ impl Table for CollectorProgress {
193
213
"aid, step, start, end"
194
214
}
195
215
216
+ fn postgres_attributes ( ) -> & ' static str {
217
+ "aid, step, start_time, end_time"
218
+ }
219
+
196
220
fn postgres_generated_id_attribute ( ) -> Option < & ' static str > {
197
221
None
198
222
}
@@ -232,6 +256,10 @@ impl Table for Error {
232
256
"aid, benchmark, error"
233
257
}
234
258
259
+ fn postgres_attributes ( ) -> & ' static str {
260
+ "aid, benchmark, error"
261
+ }
262
+
235
263
fn postgres_generated_id_attribute ( ) -> Option < & ' static str > {
236
264
None
237
265
}
@@ -266,6 +294,10 @@ impl Table for Pstat {
266
294
"series, aid, cid, value"
267
295
}
268
296
297
+ fn postgres_attributes ( ) -> & ' static str {
298
+ "series, aid, cid, value"
299
+ }
300
+
269
301
fn postgres_generated_id_attribute ( ) -> Option < & ' static str > {
270
302
None
271
303
}
@@ -303,6 +335,10 @@ impl Table for PstatSeries {
303
335
"id, crate, profile, scenario, backend, metric"
304
336
}
305
337
338
+ fn postgres_attributes ( ) -> & ' static str {
339
+ "id, crate, profile, scenario, backend, metric"
340
+ }
341
+
306
342
fn postgres_generated_id_attribute ( ) -> Option < & ' static str > {
307
343
Some ( "id" )
308
344
}
@@ -345,6 +381,10 @@ impl Table for PullRequestBuild {
345
381
"bors_sha, pr, parent_sha, complete, requested, include, exclude, runs, commit_date"
346
382
}
347
383
384
+ fn postgres_attributes ( ) -> & ' static str {
385
+ "bors_sha, pr, parent_sha, complete, requested, include, exclude, runs, commit_date"
386
+ }
387
+
348
388
fn postgres_generated_id_attribute ( ) -> Option < & ' static str > {
349
389
None
350
390
}
@@ -393,6 +433,10 @@ impl Table for RawSelfProfile {
393
433
"aid, cid, crate, profile, cache"
394
434
}
395
435
436
+ fn postgres_attributes ( ) -> & ' static str {
437
+ "aid, cid, crate, profile, cache"
438
+ }
439
+
396
440
fn postgres_generated_id_attribute ( ) -> Option < & ' static str > {
397
441
None
398
442
}
@@ -429,6 +473,10 @@ impl Table for RustcCompilation {
429
473
"aid, cid, crate, duration"
430
474
}
431
475
476
+ fn postgres_attributes ( ) -> & ' static str {
477
+ "aid, cid, crate, duration"
478
+ }
479
+
432
480
fn postgres_generated_id_attribute ( ) -> Option < & ' static str > {
433
481
None
434
482
}
@@ -597,10 +645,24 @@ async fn copy<T: Table>(
597
645
// unlikely that we will ever execute SQL built from external strings.
598
646
let table = T :: name ( ) ;
599
647
648
+ let postgres_columns = postgres
649
+ . query (
650
+ r#"SELECT column_name FROM information_schema.columns
651
+ WHERE table_schema = 'public' AND table_name = $1
652
+ ORDER BY ordinal_position"# ,
653
+ & [ & table] ,
654
+ )
655
+ . await
656
+ . unwrap ( )
657
+ . into_iter ( )
658
+ . map ( |row| row. get ( 0 ) )
659
+ . collect :: < Vec < String > > ( ) ;
660
+ let attributes = mapping_pg_columns_to_attributes :: < T > ( & postgres_columns) ;
661
+
600
662
let copy = postgres
601
663
. prepare ( & format ! (
602
- r#"copy {} from stdin (encoding utf8, format csv, null '{}')"# ,
603
- table, NULL_STRING ,
664
+ r#"copy {} ({}) from stdin (encoding utf8, format csv, null '{}')"# ,
665
+ table, attributes , NULL_STRING ,
604
666
) )
605
667
. await
606
668
. unwrap ( ) ;
@@ -707,6 +769,46 @@ fn postgres_csv_writer<W: Write>(w: W) -> csv::Writer<W> {
707
769
csv:: WriterBuilder :: new ( ) . has_headers ( false ) . from_writer ( w)
708
770
}
709
771
772
+ /// # Panics
773
+ /// Panics if the number of `sqlite_attributes` and `postgres_attributes` is mismatched or
774
+ /// a corresponding attribute for the column is not found.
775
+ fn mapping_pg_columns_to_attributes < T : Table > ( postgres_columns : & [ impl AsRef < str > ] ) -> String {
776
+ // We assume that the attributes between SQLite and Postgres have equal lengths.
777
+ // Additionally, the attribute names of Postgres should exactly match the column names in the
778
+ // database. We then attempt to reorder the attributes to mitigate the ordering difference
779
+ // between versions.
780
+ let sl = T :: sqlite_attributes ( )
781
+ . split ( ',' )
782
+ . map ( str:: trim)
783
+ . collect :: < Vec < _ > > ( ) ;
784
+ let pg = T :: postgres_attributes ( )
785
+ . split ( ',' )
786
+ . map ( str:: trim)
787
+ . collect :: < Vec < _ > > ( ) ;
788
+ assert_eq ! (
789
+ sl. len( ) ,
790
+ pg. len( ) ,
791
+ "The number of attributes in SQLite and Postgres is mismatched."
792
+ ) ;
793
+ let map = pg
794
+ . iter ( )
795
+ . enumerate ( )
796
+ . map ( |( i, p) | ( p, i) )
797
+ . collect :: < HashMap < _ , _ > > ( ) ;
798
+ let mut out_attrs = vec ! [ "" ; pg. len( ) ] ;
799
+ for col in postgres_columns. iter ( ) . map ( AsRef :: as_ref) {
800
+ let idx = map. get ( & col) . unwrap_or_else ( || {
801
+ panic ! (
802
+ "Failed to find a corresponding attribute for column {} in table {}." ,
803
+ col,
804
+ T :: name( )
805
+ )
806
+ } ) ;
807
+ out_attrs[ * idx] = col;
808
+ }
809
+ out_attrs. join ( ", " )
810
+ }
811
+
710
812
async fn get_tables ( postgres : & tokio_postgres:: Transaction < ' _ > ) -> Vec < String > {
711
813
postgres
712
814
. query (
0 commit comments