Skip to content
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
26 changes: 26 additions & 0 deletions src/ast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
pub use sqlparser::ast::{
helpers::attached_token::AttachedToken, AlterColumnOperation, AlterTable, AlterTableOperation,
AlterType, AlterTypeAddValue, AlterTypeAddValuePosition, AlterTypeOperation,
AlterTypeRenameValue, ColumnDef, ColumnOption, ColumnOptionDef, CreateDomain, CreateExtension,
CreateIndex, CreateTable, DropDomain, DropExtension, GeneratedAs, ObjectName, ObjectNamePart,
ObjectType, ReferentialAction, RenameTableNameKind, Statement, UserDefinedTypeRepresentation,
};

/// This is a copy of [`Statement::CreateType`].
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct CreateType {
/// Type name to create.
pub name: ObjectName,
/// Optional type representation details.
pub representation: Option<UserDefinedTypeRepresentation>,
}

impl From<CreateType> for Statement {
fn from(value: CreateType) -> Self {
Statement::CreateType {
name: value.name,
representation: value.representation,
}
}
}
102 changes: 83 additions & 19 deletions src/bin/sql-schema.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::{
fmt,
fs::{self, File, OpenOptions},
io::{self, Write},
process::{self},
Expand All @@ -12,7 +13,7 @@ use clap::{Parser, Subcommand};
use sql_schema::{
name_gen,
path_template::{PathTemplate, TemplateData, UpDown},
Dialect, SyntaxTree,
SyntaxTree, TreeDiffer, TreeMigrator,
};

#[derive(Parser, Debug)]
Expand Down Expand Up @@ -46,6 +47,30 @@ struct SchemaCommand {
dialect: Dialect,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, clap::ValueEnum)]
#[clap(rename_all = "lower")]
#[non_exhaustive]
pub enum Dialect {
#[default]
Generic,
PostgreSql,
SQLite,
}

impl fmt::Display for Dialect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// NOTE: this must match how clap::ValueEnum displays variants
write!(
f,
"{}",
format!("{self:?}")
.to_ascii_lowercase()
.split('-')
.collect::<String>()
)
}
}

#[derive(Parser, Debug)]
struct MigrationCommand {
/// path to schema file
Expand Down Expand Up @@ -102,15 +127,44 @@ fn main() {
}
}

macro_rules! match_dialect {
( $dialect:expr, $expr:expr ) => {
match $dialect {
Dialect::Generic => {
let dialect = sql_schema::dialect::Generic::default();
$expr(dialect)
}
Dialect::PostgreSql => {
let dialect = sql_schema::dialect::PostgreSQL::default();
$expr(dialect)
}
Dialect::SQLite => {
let dialect = sql_schema::dialect::SQLite::default();
$expr(dialect)
}
}
};
}

/// create or update schema file from migrations
fn run_schema(command: SchemaCommand) -> anyhow::Result<()> {
ensure_schema_file(&command.schema_path)?;
ensure_migration_dir(&command.migrations_dir)?;

let (migrations, _) = parse_migrations(command.dialect, &command.migrations_dir)?;
let schema = parse_sql_file(command.dialect, &command.schema_path)?;
match_dialect!(&command.dialect, |dialect| run_schema_inner(
dialect, command
))
}

fn run_schema_inner<D>(dialect: D, command: SchemaCommand) -> anyhow::Result<()>
where
D: TreeDiffer + TreeMigrator + sql_schema::Parse,
{
let (migrations, _) = parse_migrations(dialect.clone(), &command.migrations_dir)?;
let schema = parse_sql_file(dialect, &command.schema_path)?;

let diff = schema.diff(&migrations)?.unwrap_or_else(SyntaxTree::empty);
let schema = schema.migrate(&diff)?.unwrap_or_else(SyntaxTree::empty);
let schema = schema.migrate(&diff)?;
eprintln!("writing {}", command.schema_path);
OpenOptions::new()
.write(true)
Expand All @@ -126,9 +180,18 @@ fn run_migration(command: MigrationCommand) -> anyhow::Result<()> {
ensure_schema_file(&command.schema_path)?;
ensure_migration_dir(&command.migrations_dir)?;

let (migrations, opts) = parse_migrations(command.dialect, &command.migrations_dir)?;
match_dialect!(&command.dialect, |dialect| run_migration_inner(
dialect, command
))
}

fn run_migration_inner<D>(dialect: D, command: MigrationCommand) -> anyhow::Result<()>
where
D: TreeDiffer + TreeMigrator + sql_schema::Parse,
{
let (migrations, opts) = parse_migrations(dialect.clone(), &command.migrations_dir)?;
let opts = opts.reconcile(&command);
let schema = parse_sql_file(command.dialect, &command.schema_path)?;
let schema = parse_sql_file(dialect, &command.schema_path)?;
match migrations.diff(&schema)? {
Some(up_migration) => {
let name = if opts.num_migrations == 0 {
Expand Down Expand Up @@ -191,7 +254,7 @@ fn run_migration(command: MigrationCommand) -> anyhow::Result<()> {
}
}

fn write_migration(migration: SyntaxTree, path: &Utf8Path) -> anyhow::Result<()> {
fn write_migration<Dialect>(migration: SyntaxTree<Dialect>, path: &Utf8Path) -> anyhow::Result<()> {
eprintln!("writing {path}");
if let Some(parent) = path.parent() {
eprintln!("creating {parent}");
Expand Down Expand Up @@ -228,20 +291,23 @@ fn ensure_migration_dir(dir: &Utf8Path) -> anyhow::Result<()> {
Ok(())
}

fn parse_sql_file(dialect: Dialect, path: &Utf8Path) -> anyhow::Result<SyntaxTree> {
fn parse_sql_file<Dialect>(dialect: Dialect, path: &Utf8Path) -> anyhow::Result<SyntaxTree<Dialect>>
where
Dialect: sql_schema::Parse,
{
let data = fs::read_to_string(path)?;
SyntaxTree::builder()
.dialect(dialect)
.sql(data.as_str())
.build()
.context(format!("path: {path}"))
let data = data.as_str();
SyntaxTree::parse(dialect, data).context(format!("path: {path}"))
}

/// builds a [SyntaxTree] by applying each migration in order
fn parse_migrations(
fn parse_migrations<Dialect>(
dialect: Dialect,
dir: &Utf8Path,
) -> anyhow::Result<(SyntaxTree, MigrationOptions)> {
) -> anyhow::Result<(SyntaxTree<Dialect>, MigrationOptions)>
where
Dialect: TreeDiffer + TreeMigrator + sql_schema::Parse,
{
fn process_dir_entry(
entry: io::Result<Utf8DirEntry>,
) -> anyhow::Result<Option<Vec<Utf8PathBuf>>> {
Expand Down Expand Up @@ -308,10 +374,8 @@ fn parse_migrations(
.iter()
.try_fold(SyntaxTree::empty(), |schema, path| -> anyhow::Result<_> {
eprintln!("parsing {path}");
let migration = parse_sql_file(dialect, path)?;
let schema = schema
.migrate(&migration)?
.unwrap_or_else(SyntaxTree::empty);
let migration = parse_sql_file(dialect.clone(), path)?;
let schema = schema.migrate(&migration)?;
Ok(schema)
})?;
Ok((tree, opts))
Expand Down
14 changes: 14 additions & 0 deletions src/dialect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::sealed::Sealed;

#[derive(Debug, Default, Clone)]
pub struct Generic;

#[derive(Debug, Default, Clone)]
pub struct PostgreSQL;

#[derive(Debug, Default, Clone)]
pub struct SQLite;

impl Sealed for Generic {}
impl Sealed for PostgreSQL {}
impl Sealed for SQLite {}
Loading
Loading