Skip to content

Commit c8960a1

Browse files
authored
Merge pull request #1828 from eth3lbert/fix-sqlite2pg
Correct sqlite-to-postgres column mapping
2 parents 4d2e44e + a9e6714 commit c8960a1

File tree

1 file changed

+104
-2
lines changed

1 file changed

+104
-2
lines changed

database/src/bin/sqlite-to-postgres.rs

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use bytes::{BufMut, Bytes, BytesMut};
77
use chrono::{DateTime, TimeZone, Utc};
88
use database::pool::{postgres, sqlite, ConnectionManager};
99
use futures_util::sink::SinkExt;
10+
use hashbrown::HashMap;
1011
use serde::{Serialize, Serializer};
1112
use std::convert::{TryFrom, TryInto};
1213
use std::io::Write;
@@ -21,6 +22,9 @@ trait Table {
2122
/// Comma-separated list of table's attribute names in SQLite.
2223
fn sqlite_attributes() -> &'static str;
2324

25+
/// Comma-separated list of table's attribute names in Postgres.
26+
fn postgres_attributes() -> &'static str;
27+
2428
/// Name of `generated always as identity` attribute in Postgres,
2529
/// if applicable.
2630
fn postgres_generated_id_attribute() -> Option<&'static str>;
@@ -56,6 +60,10 @@ impl Table for Artifact {
5660
"id, name, date, type"
5761
}
5862

63+
fn postgres_attributes() -> &'static str {
64+
"id, name, date, type"
65+
}
66+
5967
fn postgres_generated_id_attribute() -> Option<&'static str> {
6068
Some("id")
6169
}
@@ -92,6 +100,10 @@ impl Table for ArtifactCollectionDuration {
92100
"aid, date_recorded, duration"
93101
}
94102

103+
fn postgres_attributes() -> &'static str {
104+
"aid, date_recorded, duration"
105+
}
106+
95107
fn postgres_generated_id_attribute() -> Option<&'static str> {
96108
None
97109
}
@@ -128,6 +140,10 @@ impl Table for Benchmark {
128140
"name, stabilized, category"
129141
}
130142

143+
fn postgres_attributes() -> &'static str {
144+
"name, stabilized, category"
145+
}
146+
131147
fn postgres_generated_id_attribute() -> Option<&'static str> {
132148
None
133149
}
@@ -160,6 +176,10 @@ impl Table for Collection {
160176
"id, perf_commit"
161177
}
162178

179+
fn postgres_attributes() -> &'static str {
180+
"id, perf_commit"
181+
}
182+
163183
fn postgres_generated_id_attribute() -> Option<&'static str> {
164184
Some("id")
165185
}
@@ -193,6 +213,10 @@ impl Table for CollectorProgress {
193213
"aid, step, start, end"
194214
}
195215

216+
fn postgres_attributes() -> &'static str {
217+
"aid, step, start_time, end_time"
218+
}
219+
196220
fn postgres_generated_id_attribute() -> Option<&'static str> {
197221
None
198222
}
@@ -232,6 +256,10 @@ impl Table for Error {
232256
"aid, benchmark, error"
233257
}
234258

259+
fn postgres_attributes() -> &'static str {
260+
"aid, benchmark, error"
261+
}
262+
235263
fn postgres_generated_id_attribute() -> Option<&'static str> {
236264
None
237265
}
@@ -266,6 +294,10 @@ impl Table for Pstat {
266294
"series, aid, cid, value"
267295
}
268296

297+
fn postgres_attributes() -> &'static str {
298+
"series, aid, cid, value"
299+
}
300+
269301
fn postgres_generated_id_attribute() -> Option<&'static str> {
270302
None
271303
}
@@ -303,6 +335,10 @@ impl Table for PstatSeries {
303335
"id, crate, profile, scenario, backend, metric"
304336
}
305337

338+
fn postgres_attributes() -> &'static str {
339+
"id, crate, profile, scenario, backend, metric"
340+
}
341+
306342
fn postgres_generated_id_attribute() -> Option<&'static str> {
307343
Some("id")
308344
}
@@ -345,6 +381,10 @@ impl Table for PullRequestBuild {
345381
"bors_sha, pr, parent_sha, complete, requested, include, exclude, runs, commit_date"
346382
}
347383

384+
fn postgres_attributes() -> &'static str {
385+
"bors_sha, pr, parent_sha, complete, requested, include, exclude, runs, commit_date"
386+
}
387+
348388
fn postgres_generated_id_attribute() -> Option<&'static str> {
349389
None
350390
}
@@ -393,6 +433,10 @@ impl Table for RawSelfProfile {
393433
"aid, cid, crate, profile, cache"
394434
}
395435

436+
fn postgres_attributes() -> &'static str {
437+
"aid, cid, crate, profile, cache"
438+
}
439+
396440
fn postgres_generated_id_attribute() -> Option<&'static str> {
397441
None
398442
}
@@ -429,6 +473,10 @@ impl Table for RustcCompilation {
429473
"aid, cid, crate, duration"
430474
}
431475

476+
fn postgres_attributes() -> &'static str {
477+
"aid, cid, crate, duration"
478+
}
479+
432480
fn postgres_generated_id_attribute() -> Option<&'static str> {
433481
None
434482
}
@@ -597,10 +645,24 @@ async fn copy<T: Table>(
597645
// unlikely that we will ever execute SQL built from external strings.
598646
let table = T::name();
599647

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+
600662
let copy = postgres
601663
.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,
604666
))
605667
.await
606668
.unwrap();
@@ -707,6 +769,46 @@ fn postgres_csv_writer<W: Write>(w: W) -> csv::Writer<W> {
707769
csv::WriterBuilder::new().has_headers(false).from_writer(w)
708770
}
709771

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+
710812
async fn get_tables(postgres: &tokio_postgres::Transaction<'_>) -> Vec<String> {
711813
postgres
712814
.query(

0 commit comments

Comments
 (0)