Skip to content
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

chore(sqlx): Refactor to use async Sqlx #8

Merged
merged 1 commit into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,251 changes: 996 additions & 255 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ version = "0.0.0"
edition = "2021"

[dependencies]
clap = { version = "4.5.4", features = ["derive"] }
colored = "2.1.0"
postgres = "0.19.7"
clap = { version = "=4.5.20", features = ["derive"] }
colored = "=2.1.0"
itertools = "=0.13.0"
sqlformat = "=0.2.6"
sqlx = { version = "=0.8.2", features = ["runtime-tokio", "postgres", "macros"] }
tokio = { version = "=1.40.0", features = ["rt", "rt-multi-thread", "macros", "signal"] }
futures-core = "=0.3.31"
futures = "=0.3.31"
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@

Little Bobby Diff Tool is a CLI tool to compare database schemas.

It currently compares the following across one or more schemas.
RDBMS support:

- [ ] MySQL
- [ ] Oracle
- [X] PostgreSQL
- [ ] SQLite
- [ ] SQL Server

Items compared:

- [X] Columns
- [X] Column Privileges
- [ ] Indices
- [X] Routines
- [X] Routine Privileges
- [X] Sequences
Expand Down
6 changes: 2 additions & 4 deletions db/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
version: '2'

services:
left:
image: postgres
image: postgres:latest
container_name: postgres-left
environment:
- POSTGRES_PASSWORD=postgres
ports:
- 8901:5432
restart: unless-stopped
right:
image: postgres
image: postgres:latest
container_name: postgres-right
environment:
- POSTGRES_PASSWORD=postgres
Expand Down
16 changes: 8 additions & 8 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod args;
use std::process;
use clap::{Parser};
use colored::Colorize;
use postgres::Error;
use sqlx::Error;

use crate::{compare, db};
use crate::cli::args::{Args, Colouring::Always, Colouring::Never};
Expand All @@ -20,8 +20,8 @@ use crate::compare::report::sequence::SequenceComparison;
use crate::compare::report::sequence::SequenceComparison::{SequenceAdded, SequenceMaintained, SequenceRemoved};
use crate::compare::report::table::TableComparison;
use crate::compare::report::table::TableComparison::{TableAdded, TableMaintained, TableRemoved};
use crate::compare::report::table_column::TableColumnComparison;
use crate::compare::report::table_column::TableColumnComparison::{ColumnAdded, ColumnMaintained, ColumnRemoved};
use crate::compare::report::column::ColumnComparison;
use crate::compare::report::column::ColumnComparison::{ColumnAdded, ColumnMaintained, ColumnRemoved};
use crate::compare::report::table_constraint::TableConstraintComparison;
use crate::compare::report::table_constraint::TableConstraintComparison::{ConstraintAdded, ConstraintMaintained, ConstraintRemoved};
use crate::compare::report::table_trigger::TableTriggerComparison::{TriggerAdded, TriggerMaintained, TriggerRemoved};
Expand Down Expand Up @@ -51,9 +51,9 @@ impl CLI {
CLI { args }
}

pub fn run(&self) -> Result<i32, Error> {
let left_db = db::Database::connect(self.args.left.as_str())?;
let right_db = db::Database::connect(self.args.right.as_str())?;
pub async fn run(&self) -> Result<i32, Error> {
let left_db = db::Database::connect(self.args.left.as_str()).await?;
let right_db = db::Database::connect(self.args.right.as_str()).await?;

let mut comparer = compare::Comparer::new(
left_db,
Expand All @@ -64,7 +64,7 @@ impl CLI {

let mut differences = 0;

let report = comparer.compare(self.args.schema.clone())?;
let report = comparer.compare(self.args.schema.clone()).await?;
differences += self.render_schema_report(report);

process::exit(differences);
Expand Down Expand Up @@ -286,7 +286,7 @@ impl CLI {
differences
}

fn render_table_column_report(&self, report: &Report<TableColumnComparison>) -> i32 {
fn render_table_column_report(&self, report: &Report<ColumnComparison>) -> i32 {
let mut differences = 0;

for column in &report.entries {
Expand Down
624 changes: 399 additions & 225 deletions src/compare/mod.rs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ use crate::compare::report::privilege::{PrivilegeComparison};
use crate::compare::report::property::{PropertyComparison};
use crate::compare::report::{HasChanges, Report};

pub enum TableColumnComparison {
pub enum ColumnComparison {
ColumnAdded { column_name: String },
ColumnRemoved { column_name: String },
ColumnMaintained { column_name: String, properties: Report<PropertyComparison>, privileges: Report<PrivilegeComparison> }
}

impl HasChanges for TableColumnComparison {
impl HasChanges for ColumnComparison {
fn has_changes(&self) -> bool {
match self {
TableColumnComparison::ColumnAdded { .. } | TableColumnComparison::ColumnRemoved { .. } => true,
TableColumnComparison::ColumnMaintained { column_name: _column_name, properties, privileges } =>
ColumnComparison::ColumnAdded { .. } | ColumnComparison::ColumnRemoved { .. } => true,
ColumnComparison::ColumnMaintained { column_name: _column_name, properties, privileges } =>
properties.has_changes() |
privileges.has_changes(),
}
Expand Down
2 changes: 1 addition & 1 deletion src/compare/report/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod privilege;
pub mod routine;
pub mod sequence;
pub mod table;
pub mod table_column;
pub mod column;
pub mod table_constraint;
pub mod table_trigger;
pub mod view;
Expand Down
4 changes: 2 additions & 2 deletions src/compare/report/table.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use crate::compare::report::{HasChanges, Report};
use crate::compare::report::privilege::PrivilegeComparison;
use crate::compare::report::property::PropertyComparison;
use crate::compare::report::table_column::TableColumnComparison;
use crate::compare::report::column::ColumnComparison;
use crate::compare::report::table_constraint::TableConstraintComparison;
use crate::compare::report::table_trigger::TableTriggerComparison;

pub enum TableComparison {
TableAdded { table_name: String },
TableRemoved { table_name: String },
TableMaintained { table_name: String, properties: Report<PropertyComparison>, columns: Report<TableColumnComparison>, privileges: Report<PrivilegeComparison>, constraints: Report<TableConstraintComparison>, triggers: Report<TableTriggerComparison> },
TableMaintained { table_name: String, properties: Report<PropertyComparison>, columns: Report<ColumnComparison>, privileges: Report<PrivilegeComparison>, constraints: Report<TableConstraintComparison>, triggers: Report<TableTriggerComparison> },
}

impl HasChanges for TableComparison {
Expand Down
112 changes: 112 additions & 0 deletions src/db/column.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use sqlx::{Error, FromRow, PgConnection};

const QUERY: &str = r#"
SELECT
table_catalog,
table_schema,
table_name,
column_name,
ordinal_position,
column_default,
is_nullable,
data_type,
character_maximum_length,
character_octet_length,
numeric_precision,
numeric_precision_radix,
numeric_scale,
datetime_precision,
interval_type,
interval_precision,
character_set_catalog,
character_set_schema,
character_set_name,
collation_catalog,
collation_schema,
collation_name,
domain_catalog,
domain_schema,
domain_name,
udt_catalog,
udt_schema,
udt_name,
scope_catalog,
scope_schema,
scope_name,
maximum_cardinality,
dtd_identifier,
is_self_referencing,
is_identity,
identity_generation,
identity_start,
identity_increment,
identity_maximum,
identity_minimum,
identity_cycle,
is_generated,
generation_expression,
is_updatable
FROM
information_schema.columns
WHERE
table_schema = ANY($1)
ORDER BY
table_catalog,
table_schema,
table_name,
column_name;"#;

pub async fn columns(connection: &mut PgConnection, schema_names: &[String]) -> Result<Vec<Column>, Error> {
sqlx::query_as(QUERY)
.bind(&schema_names[..])
.fetch_all(connection).await
}

#[derive(Debug, Clone, PartialEq, FromRow)]
pub struct Column {
pub table_catalog: String,
pub table_schema: String,
pub table_name: String,
pub column_name: String,
pub ordinal_position: i32,
pub column_default: Option<String>,
pub is_nullable: String,
pub data_type: String,
pub character_maximum_length: Option<i32>,
pub character_octet_length: Option<i32>,
pub numeric_precision: Option<i32>,
pub numeric_precision_radix: Option<i32>,
pub numeric_scale: Option<i32>,
pub datetime_precision: Option<i32>,
pub interval_type: Option<String>,
pub interval_precision: Option<i32>,
pub character_set_catalog: Option<String>,
pub character_set_schema: Option<String>,
pub character_set_name: Option<String>,
pub collation_catalog: Option<String>,
pub collation_schema: Option<String>,
pub collation_name: Option<String>,
pub domain_catalog: Option<String>,
pub domain_schema: Option<String>,
pub domain_name: Option<String>,
pub udt_catalog: Option<String>,
pub udt_schema: Option<String>,
pub udt_name: Option<String>,
pub scope_catalog: Option<String>,
pub scope_schema: Option<String>,
pub scope_name: Option<String>,
pub maximum_cardinality: Option<i32>,
pub dtd_identifier: Option<String>,
pub is_self_referencing: String,
pub is_identity: String,
pub identity_generation: Option<String>,
pub identity_start: Option<String>,
pub identity_increment: Option<String>,
pub identity_maximum: Option<String>,
pub identity_minimum: Option<String>,
pub identity_cycle: Option<String>,
pub is_generated: String,
pub generation_expression: Option<String>,
pub is_updatable: String,
}

57 changes: 57 additions & 0 deletions src/db/column_privilege.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use sqlx::{Error, FromRow, PgConnection};
use crate::db::privilege::Privilege;

const QUERY: &str = r#"
SELECT
grantor,
grantee,
table_catalog,
table_schema,
table_name,
column_name,
privilege_type,
is_grantable
FROM
information_schema.column_privileges
WHERE
table_schema = ANY($1)
ORDER BY
table_catalog,
table_schema,
table_name,
column_name,
grantor,
grantee,
privilege_type;"#;

pub async fn column_privileges(connection: &mut PgConnection, schema_names: &[String]) -> Result<Vec<ColumnPrivilege>, Error> {
sqlx::query_as(QUERY)
.bind(&schema_names[..])
.fetch_all(connection).await
}

#[derive(Debug, Clone, PartialEq, FromRow)]
pub struct ColumnPrivilege {
pub grantor: String,
pub grantee: String,
pub table_catalog: String,
pub table_schema: String,
pub table_name: String,
pub column_name: String,
pub privilege_type: String,
pub is_grantable: String,
}

impl Privilege for &ColumnPrivilege {
fn grantor(&self) -> &str {
&self.grantor
}

fn grantee(&self) -> &str {
&self.grantee
}

fn privilege_type(&self) -> &str {
&self.privilege_type
}
}
Loading
Loading