Skip to content

Adds support for Views. #4077

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
[feat] diesel cli generates views.
  • Loading branch information
coderPaddyS committed Jun 20, 2024
commit a38bbc13f83c8edf1c9fe5ea8a853bdbd1db62db
75 changes: 73 additions & 2 deletions diesel_cli/src/infer_schema_internals/data_structures.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use diesel_table_macro_syntax::ColumnDef;

use super::table_data::TableName;
use super::{table_data::TableName, TableData, ViewData};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColumnInformation {
@@ -79,7 +79,10 @@ impl ColumnType {
}
}

use std::fmt;
use std::{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor stylistic nit: Diesel we use unnested imports in all other parts of the code base, so I would like to keep this consistent.

fmt::{self, Display},
str::FromStr,
};

impl fmt::Display for ColumnType {
fn fmt(&self, out: &mut fmt::Formatter) -> Result<(), fmt::Error> {
@@ -156,3 +159,71 @@ impl ForeignKeyConstraint {
)
}
}

#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum SupportedColumnStructures {
VIEW,
TABLE,
}

#[derive(Debug)]
pub enum ColumnData {
View(ViewData),
Table(TableData),
}

impl ColumnData {
pub fn table_name(&self) -> &TableName {
match &self {
Self::Table(table) => &table.name,
Self::View(view) => &view.name,
}
}

pub fn columns(&self) -> &Vec<ColumnDefinition> {
match self {
Self::Table(table) => &table.column_data,
Self::View(view) => &view.column_data,
}
}

pub fn comment(&self) -> &Option<String> {
match self {
Self::Table(table) => &table.comment,
Self::View(view) => &view.comment,
}
}
}

impl Display for SupportedColumnStructures {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let format = match self {
Self::TABLE => "BASE TABLE",
Self::VIEW => "VIEW",
};
write!(f, "{format}")
}
}

impl FromStr for SupportedColumnStructures {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"BASE TABLE" => Ok(Self::TABLE),
"VIEW" => Ok(Self::VIEW),
_ => unreachable!("This should never happen. Read {s}"),
}
}
}

impl SupportedColumnStructures {
pub fn display_all() -> Vec<String> {
SupportedColumnStructures::all()
.into_iter()
.map(|s| s.to_string())
.collect()
}
pub fn all() -> Vec<SupportedColumnStructures> {
vec![Self::VIEW, Self::TABLE]
}
}
68 changes: 57 additions & 11 deletions diesel_cli/src/infer_schema_internals/inference.rs
Original file line number Diff line number Diff line change
@@ -111,7 +111,7 @@ pub fn rust_name_for_sql_name(sql_name: &str) -> String {
pub fn load_table_names(
connection: &mut InferConnection,
schema_name: Option<&str>,
) -> Result<Vec<TableName>, crate::errors::Error> {
) -> Result<Vec<(SupportedColumnStructures, TableName)>, crate::errors::Error> {
let tables = match connection {
#[cfg(feature = "sqlite")]
InferConnection::Sqlite(ref mut c) => super::sqlite::load_table_names(c, schema_name),
@@ -129,10 +129,24 @@ pub fn load_table_names(
Ok(tables)
}

pub fn filter_table_names(table_names: Vec<TableName>, table_filter: &Filtering) -> Vec<TableName> {
pub fn filter_column_structure(
table_names: &Vec<(SupportedColumnStructures, TableName)>,
structure: SupportedColumnStructures,
) -> Vec<TableName> {
table_names
.into_iter()
.filter(|t| !table_filter.should_ignore_table(t))
.filter_map(|(s, t)| if *s == structure { Some(t) } else { None })
.cloned()
.collect()
}

pub fn filter_table_names(
table_names: Vec<(SupportedColumnStructures, TableName)>,
table_filter: &Filtering,
) -> Vec<(SupportedColumnStructures, TableName)> {
table_names
.into_iter()
.filter(|(_, t)| !table_filter.should_ignore_table(t))
.collect::<_>()
}

@@ -261,11 +275,12 @@ pub fn load_foreign_key_constraints(
}

#[tracing::instrument(skip(connection))]
pub fn load_table_data(
fn load_column_structure_data(
connection: &mut InferConnection,
name: TableName,
name: &TableName,
config: &PrintSchema,
) -> Result<TableData, crate::errors::Error> {
primary_key: Option<&Vec<String>>,
) -> Result<(Option<String>, Vec<ColumnDefinition>), crate::errors::Error> {
// No point in loading table comments if they are not going to be displayed
let table_comment = match config.with_docs {
DocConfig::NoDocComments => None,
@@ -275,12 +290,17 @@ pub fn load_table_data(
}
};

let primary_key = get_primary_keys(connection, &name)?;

let column_data = get_column_information(connection, &name, &config.column_sorting)?
get_column_information(connection, &name, &config.column_sorting)?
.into_iter()
.map(|c| {
let ty = determine_column_type(&c, connection, &name, &primary_key, config)?;
let default_pk = vec![];
let ty = determine_column_type(
&c,
connection,
&name,
primary_key.unwrap_or_else(|| &default_pk),
config,
)?;

let ColumnInformation {
column_name,
@@ -296,7 +316,19 @@ pub fn load_table_data(
comment,
})
})
.collect::<Result<_, crate::errors::Error>>()?;
.collect::<Result<Vec<ColumnDefinition>, crate::errors::Error>>()
.map(|data| (table_comment, data))
}

#[tracing::instrument(skip(connection))]
pub fn load_table_data(
connection: &mut InferConnection,
name: TableName,
config: &PrintSchema,
) -> Result<TableData, crate::errors::Error> {
let primary_key = get_primary_keys(connection, &name)?;
let (table_comment, column_data) =
load_column_structure_data(connection, &name, config, Some(&primary_key))?;

let primary_key = primary_key
.iter()
@@ -310,3 +342,17 @@ pub fn load_table_data(
comment: table_comment,
})
}

#[tracing::instrument(skip(connection))]
pub fn load_view_data(
connection: &mut InferConnection,
name: TableName,
config: &PrintSchema,
) -> Result<ViewData, crate::errors::Error> {
let (table_comment, column_data) = load_column_structure_data(connection, &name, config, None)?;
Ok(ViewData {
name,
column_data,
comment: table_comment,
})
}
75 changes: 53 additions & 22 deletions diesel_cli/src/infer_schema_internals/information_schema.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::str::FromStr;

use diesel::backend::Backend;
use diesel::connection::LoadConnection;
@@ -12,6 +13,8 @@ use diesel::pg::Pg;
use diesel::query_builder::QueryFragment;
use diesel::*;

use crate::infer_schema_internals::SupportedColumnStructures;

use self::information_schema::{key_column_usage, table_constraints, tables};
use super::inference;
use super::table_data::TableName;
@@ -140,20 +143,20 @@ where
pub fn load_table_names<'a, Conn>(
connection: &mut Conn,
schema_name: Option<&'a str>,
) -> Result<Vec<TableName>, crate::errors::Error>
) -> Result<Vec<(SupportedColumnStructures, TableName)>, crate::errors::Error>
where
Conn: LoadConnection,
Conn::Backend: DefaultSchema + 'static,
String: FromSql<sql_types::Text, Conn::Backend>,
Filter<
Filter<
Filter<
Select<tables::table, tables::table_name>,
Select<tables::table, (tables::table_name, tables::table_type)>,
Eq<tables::table_schema, Cow<'a, str>>,
>,
NotLike<tables::table_name, &'static str>,
>,
Like<tables::table_type, &'static str>,
EqAny<tables::table_type, Vec<String>>,
>: QueryFragment<Conn::Backend>,
Conn::Backend: QueryMetadata<sql_types::Text>,
{
@@ -165,20 +168,24 @@ where
.unwrap_or_else(|| Cow::Owned(default_schema.clone()));

let mut table_names = tables
.select(table_name)
.select((table_name, table_type))
.filter(table_schema.eq(db_schema_name))
.filter(table_name.not_like("\\_\\_%"))
.filter(table_type.like("BASE TABLE"))
.load::<String>(connection)?;
.filter(table_type.eq_any(SupportedColumnStructures::display_all()))
.load::<(String, String)>(connection)?;
table_names.sort_unstable();
Ok(table_names
.into_iter()
.map(|name| TableName {
rust_name: inference::rust_name_for_sql_name(&name),
sql_name: name,
schema: schema_name
.filter(|&schema| schema != default_schema)
.map(|schema| schema.to_owned()),
.map(|(name, tpy)| {
let tpy = SupportedColumnStructures::from_str(&tpy).unwrap();
let data = TableName {
rust_name: inference::rust_name_for_sql_name(&name),
sql_name: name,
schema: schema_name
.filter(|&schema| schema != default_schema)
.map(|schema| schema.to_owned()),
};
(tpy, data)
})
.collect())
}
@@ -213,7 +220,11 @@ mod tests {
.execute(&mut connection)
.unwrap();

let table_names = load_table_names(&mut connection, None).unwrap();
let table_names = load_table_names(&mut connection, None)
.unwrap()
.into_iter()
.map(|(_, table)| table)
.collect::<Vec<_>>();

assert!(table_names.contains(&TableName::from_name("a_regular_table")));
assert!(!table_names.contains(&TableName::from_name("a_view")));
@@ -229,12 +240,16 @@ mod tests {
.unwrap();

let table_names = load_table_names(&mut connection, None).unwrap();
for TableName { schema, .. } in &table_names {
for (_, TableName { schema, .. }) in &table_names {
assert_eq!(None, *schema);
}
assert!(table_names.contains(&TableName::from_name(
"load_table_names_loads_from_public_schema_if_none_given",
),));
assert!(table_names
.into_iter()
.map(|(_, table)| table)
.collect::<Vec<_>>()
.contains(&TableName::from_name(
"load_table_names_loads_from_public_schema_if_none_given",
),));
}

#[test]
@@ -248,14 +263,22 @@ mod tests {
.execute(&mut connection)
.unwrap();

let table_names = load_table_names(&mut connection, Some("test_schema")).unwrap();
let table_names = load_table_names(&mut connection, Some("test_schema"))
.unwrap()
.into_iter()
.map(|(_, table)| table)
.collect::<Vec<_>>();
assert_eq!(vec![TableName::new("table_1", "test_schema")], table_names);

diesel::sql_query("CREATE TABLE test_schema.table_2 (id SERIAL PRIMARY KEY)")
.execute(&mut connection)
.unwrap();

let table_names = load_table_names(&mut connection, Some("test_schema")).unwrap();
let table_names = load_table_names(&mut connection, Some("test_schema"))
.unwrap()
.into_iter()
.map(|(_, table)| table)
.collect::<Vec<_>>();
let expected = vec![
TableName::new("table_1", "test_schema"),
TableName::new("table_2", "test_schema"),
@@ -269,13 +292,21 @@ mod tests {
.execute(&mut connection)
.unwrap();

let table_names = load_table_names(&mut connection, Some("test_schema")).unwrap();
let table_names = load_table_names(&mut connection, Some("test_schema"))
.unwrap()
.into_iter()
.map(|(_, table)| table)
.collect::<Vec<_>>();
let expected = vec![
TableName::new("table_1", "test_schema"),
TableName::new("table_2", "test_schema"),
];
assert_eq!(expected, table_names);
let table_names = load_table_names(&mut connection, Some("other_test_schema")).unwrap();
let table_names = load_table_names(&mut connection, Some("other_test_schema"))
.unwrap()
.into_iter()
.map(|(_, table)| table)
.collect::<Vec<_>>();
assert_eq!(
vec![TableName::new("table_1", "other_test_schema")],
table_names
@@ -301,7 +332,7 @@ mod tests {
let table_names = load_table_names(&mut connection, Some("test_schema"))
.unwrap()
.iter()
.map(|table| table.to_string())
.map(|(_, table)| table.to_string())
.collect::<Vec<_>>();
assert_eq!(
vec!["test_schema.aaa", "test_schema.bbb", "test_schema.ccc"],
Loading