Skip to content

Commit b7b3670

Browse files
committed
Remove fields with a default value from insertstructs
All fields with default values are now skipped in insert structs. Unfortunately `infer_schema_internals` does not know anything about default values, so we inlined the code and added support for default values. This should be a temporary thing till someone unifies the infer schema code of wundergraph and diesel again
1 parent fa92d92 commit b7b3670

File tree

13 files changed

+1596
-6
lines changed

13 files changed

+1596
-6
lines changed

wundergraph_cli/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@ version = "0.1.0"
66
[dependencies]
77
structopt = "0.2"
88
clap = "2.27"
9-
infer_schema_internals = "1.2"
109
diesel = "1.2"
1110
clippy = { version = "0.0.207", optional = true }
1211

1312
[features]
1413
default = ["postgres", "sqlite"]
1514
lint = ["clippy"]
16-
sqlite = ["infer_schema_internals/sqlite"]
17-
postgres = ["infer_schema_internals/postgres"]
15+
sqlite = ["diesel/sqlite"]
16+
postgres = ["diesel/postgres"]

wundergraph_cli/src/database.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use diesel::*;
2+
3+
use std::error::Error;
4+
5+
enum Backend {
6+
#[cfg(feature = "postgres")]
7+
Pg,
8+
#[cfg(feature = "sqlite")]
9+
Sqlite,
10+
#[cfg(feature = "mysql")]
11+
Mysql,
12+
}
13+
14+
impl Backend {
15+
fn for_url(database_url: &str) -> Self {
16+
match database_url {
17+
#[cfg(feature = "postgres")]
18+
_ if database_url.starts_with("postgres://")
19+
|| database_url.starts_with("postgresql://") =>
20+
{
21+
Backend::Pg
22+
}
23+
#[cfg(feature = "mysql")]
24+
_ if database_url.starts_with("mysql://") =>
25+
{
26+
Backend::Mysql
27+
}
28+
#[cfg(feature = "sqlite")]
29+
_ => Backend::Sqlite,
30+
#[cfg(not(feature = "sqlite"))]
31+
_ => {
32+
let mut available_schemes: Vec<&str> = Vec::new();
33+
34+
// One of these will always be true, or you are compiling
35+
// diesel_cli without a backend. And why would you ever want to
36+
// do that?
37+
if cfg!(feature = "postgres") {
38+
available_schemes.push("`postgres://`");
39+
}
40+
if cfg!(feature = "mysql") {
41+
available_schemes.push("`mysql://`");
42+
}
43+
44+
panic!(
45+
"`{}` is not a valid database URL. It should start with {}",
46+
database_url,
47+
available_schemes.join(" or ")
48+
);
49+
}
50+
#[cfg(not(any(feature = "mysql", feature = "sqlite", feature = "postgres")))]
51+
_ => compile_error!(
52+
"At least one backend must be specified for use with this crate. \
53+
You may omit the unneeded dependencies in the following command. \n\n \
54+
ex. `cargo install diesel_cli --no-default-features --features mysql postgres sqlite` \n"
55+
),
56+
}
57+
}
58+
}
59+
60+
pub enum InferConnection {
61+
#[cfg(feature = "postgres")]
62+
Pg(PgConnection),
63+
#[cfg(feature = "sqlite")]
64+
Sqlite(SqliteConnection),
65+
#[cfg(feature = "mysql")]
66+
Mysql(MysqlConnection),
67+
}
68+
69+
impl InferConnection {
70+
pub fn establish(database_url: &str) -> Result<Self, Box<Error>> {
71+
match Backend::for_url(database_url) {
72+
#[cfg(feature = "postgres")]
73+
Backend::Pg => PgConnection::establish(database_url).map(InferConnection::Pg),
74+
#[cfg(feature = "sqlite")]
75+
Backend::Sqlite => {
76+
SqliteConnection::establish(database_url).map(InferConnection::Sqlite)
77+
}
78+
#[cfg(feature = "mysql")]
79+
Backend::Mysql => MysqlConnection::establish(database_url).map(InferConnection::Mysql),
80+
}.map_err(Into::into)
81+
}
82+
}
83+
84+
#[cfg(all(test, any(feature = "postgres", feature = "mysql")))]
85+
mod tests {
86+
use super::change_database_of_url;
87+
88+
#[test]
89+
fn split_pg_connection_string_returns_postgres_url_and_database() {
90+
let database = "database".to_owned();
91+
let base_url = "postgresql://localhost:5432".to_owned();
92+
let database_url = format!("{}/{}", base_url, database);
93+
let postgres_url = format!("{}/{}", base_url, "postgres");
94+
assert_eq!(
95+
(database, postgres_url),
96+
change_database_of_url(&database_url, "postgres")
97+
);
98+
}
99+
100+
#[test]
101+
fn split_pg_connection_string_handles_user_and_password() {
102+
let database = "database".to_owned();
103+
let base_url = "postgresql://user:password@localhost:5432".to_owned();
104+
let database_url = format!("{}/{}", base_url, database);
105+
let postgres_url = format!("{}/{}", base_url, "postgres");
106+
assert_eq!(
107+
(database, postgres_url),
108+
change_database_of_url(&database_url, "postgres")
109+
);
110+
}
111+
112+
#[test]
113+
fn split_pg_connection_string_handles_query_string() {
114+
let database = "database".to_owned();
115+
let query = "?sslmode=true".to_owned();
116+
let base_url = "postgresql://user:password@localhost:5432".to_owned();
117+
let database_url = format!("{}/{}{}", base_url, database, query);
118+
let postgres_url = format!("{}/{}{}", base_url, "postgres", query);
119+
assert_eq!(
120+
(database, postgres_url),
121+
change_database_of_url(&database_url, "postgres")
122+
);
123+
}
124+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#[cfg(any(feature = "postgres", feature = "mysql"))]
2+
use diesel::backend::Backend;
3+
use diesel::deserialize::FromSqlRow;
4+
#[cfg(feature = "sqlite")]
5+
use diesel::sqlite::Sqlite;
6+
use diesel::*;
7+
8+
#[cfg(any(feature = "postgres", feature = "mysql"))]
9+
use super::information_schema::UsesInformationSchema;
10+
use super::table_data::TableName;
11+
12+
#[derive(Debug, Clone, PartialEq, Eq)]
13+
pub struct ColumnInformation {
14+
pub column_name: String,
15+
pub type_name: String,
16+
pub nullable: bool,
17+
pub has_default: bool,
18+
}
19+
20+
#[derive(Debug)]
21+
pub struct ColumnType {
22+
pub rust_name: String,
23+
pub is_array: bool,
24+
pub is_nullable: bool,
25+
pub is_unsigned: bool,
26+
}
27+
28+
use std::fmt;
29+
30+
impl fmt::Display for ColumnType {
31+
fn fmt(&self, out: &mut fmt::Formatter) -> Result<(), fmt::Error> {
32+
if self.is_nullable {
33+
write!(out, "Nullable<")?;
34+
}
35+
if self.is_array {
36+
write!(out, "Array<")?;
37+
}
38+
if self.is_unsigned {
39+
write!(out, "Unsigned<")?;
40+
}
41+
write!(out, "{}", self.rust_name)?;
42+
if self.is_unsigned {
43+
write!(out, ">")?;
44+
}
45+
if self.is_array {
46+
write!(out, ">")?;
47+
}
48+
if self.is_nullable {
49+
write!(out, ">")?;
50+
}
51+
Ok(())
52+
}
53+
}
54+
55+
#[derive(Debug)]
56+
pub struct ColumnDefinition {
57+
pub sql_name: String,
58+
pub ty: ColumnType,
59+
pub docs: String,
60+
pub rust_name: Option<String>,
61+
pub has_default: bool,
62+
}
63+
64+
impl ColumnInformation {
65+
pub fn new<T, U>(column_name: T, type_name: U, nullable: bool, has_default: bool) -> Self
66+
where
67+
T: Into<String>,
68+
U: Into<String>,
69+
{
70+
ColumnInformation {
71+
column_name: column_name.into(),
72+
type_name: type_name.into(),
73+
nullable,
74+
has_default,
75+
}
76+
}
77+
}
78+
79+
#[cfg(any(feature = "postgres", feature = "mysql"))]
80+
impl<ST, DB> Queryable<ST, DB> for ColumnInformation
81+
where
82+
DB: Backend + UsesInformationSchema,
83+
(String, String, String, Option<String>): FromSqlRow<ST, DB>,
84+
{
85+
type Row = (String, String, String, Option<String>);
86+
87+
fn build(row: Self::Row) -> Self {
88+
ColumnInformation::new(row.0, row.1, row.2 == "YES", row.3.is_some())
89+
}
90+
}
91+
92+
#[cfg(feature = "sqlite")]
93+
impl<ST> Queryable<ST, Sqlite> for ColumnInformation
94+
where
95+
(i32, String, String, bool, Option<String>, bool): FromSqlRow<ST, Sqlite>,
96+
{
97+
type Row = (i32, String, String, bool, Option<String>, bool);
98+
99+
fn build(row: Self::Row) -> Self {
100+
ColumnInformation::new(row.1, row.2, !row.3, row.4.is_some())
101+
}
102+
}
103+
104+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
105+
pub struct ForeignKeyConstraint {
106+
pub child_table: TableName,
107+
pub parent_table: TableName,
108+
pub foreign_key: String,
109+
pub primary_key: String,
110+
}
111+
112+
impl ForeignKeyConstraint {
113+
pub fn ordered_tables(&self) -> (&TableName, &TableName) {
114+
use std::cmp::{max, min};
115+
(
116+
min(&self.parent_table, &self.child_table),
117+
max(&self.parent_table, &self.child_table),
118+
)
119+
}
120+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#![cfg_attr(feature = "cargo-clippy", allow(expect_fun_call))] // My calls are so fun
2+
3+
use super::data_structures::ForeignKeyConstraint;
4+
use super::inference::get_primary_keys;
5+
use super::table_data::TableName;
6+
use database::InferConnection;
7+
8+
pub fn remove_unsafe_foreign_keys_for_codegen(
9+
database_url: &str,
10+
foreign_keys: &[ForeignKeyConstraint],
11+
safe_tables: &[TableName],
12+
) -> Vec<ForeignKeyConstraint> {
13+
let conn = InferConnection::establish(database_url)
14+
.expect(&format!("Could not connect to `{}`", database_url));
15+
16+
let duplicates = foreign_keys
17+
.iter()
18+
.map(|fk| fk.ordered_tables())
19+
.filter(|tables| {
20+
let dup_count = foreign_keys
21+
.iter()
22+
.filter(|fk| tables == &fk.ordered_tables())
23+
.count();
24+
dup_count > 1
25+
})
26+
.collect::<Vec<_>>();
27+
28+
foreign_keys
29+
.iter()
30+
.filter(|fk| fk.parent_table != fk.child_table)
31+
.filter(|fk| safe_tables.contains(&fk.parent_table))
32+
.filter(|fk| safe_tables.contains(&fk.child_table))
33+
.filter(|fk| {
34+
let pk_columns = get_primary_keys(&conn, &fk.parent_table).expect(&format!(
35+
"Error loading primary keys for `{}`",
36+
fk.parent_table
37+
));
38+
pk_columns.len() == 1 && pk_columns[0] == fk.primary_key
39+
})
40+
.filter(|fk| !duplicates.contains(&fk.ordered_tables()))
41+
.cloned()
42+
.collect()
43+
}

0 commit comments

Comments
 (0)