|
| 1 | +//! Database migrations |
| 2 | +
|
| 3 | +use db::connect_db; |
| 4 | +use error::Result as CratesfyiResult; |
| 5 | +use postgres::error::Error as PostgresError; |
| 6 | +use postgres::transaction::Transaction; |
| 7 | +use schemamama::{Migration, Migrator, Version}; |
| 8 | +use schemamama_postgres::{PostgresAdapter, PostgresMigration}; |
| 9 | + |
| 10 | + |
| 11 | +/// Creates a new PostgresMigration from upgrade and downgrade queries. |
| 12 | +/// Downgrade query should return database to previous state. |
| 13 | +/// |
| 14 | +/// Example: |
| 15 | +/// |
| 16 | +/// ``` |
| 17 | +/// let my_migration = migration!(100, |
| 18 | +/// "Create test table", |
| 19 | +/// "CREATE TABLE test ( id SERIAL);", |
| 20 | +/// "DROP TABLE test;"); |
| 21 | +/// ``` |
| 22 | +macro_rules! migration { |
| 23 | + ($version:expr, $description:expr, $up:expr, $down:expr) => {{ |
| 24 | + struct Amigration; |
| 25 | + impl Migration for Amigration { |
| 26 | + fn version(&self) -> Version { |
| 27 | + $version |
| 28 | + } |
| 29 | + fn description(&self) -> String { |
| 30 | + $description.to_owned() |
| 31 | + } |
| 32 | + } |
| 33 | + impl PostgresMigration for Amigration { |
| 34 | + fn up(&self, transaction: &Transaction) -> Result<(), PostgresError> { |
| 35 | + info!("Applying migration {}: {}", self.version(), self.description()); |
| 36 | + transaction.batch_execute($up).map(|_| ()) |
| 37 | + } |
| 38 | + fn down(&self, transaction: &Transaction) -> Result<(), PostgresError> { |
| 39 | + info!("Removing migration {}: {}", self.version(), self.description()); |
| 40 | + transaction.batch_execute($down).map(|_| ()) |
| 41 | + } |
| 42 | + } |
| 43 | + Box::new(Amigration) |
| 44 | + }}; |
| 45 | +} |
| 46 | + |
| 47 | + |
| 48 | +pub fn migrate(version: Option<Version>) -> CratesfyiResult<()> { |
| 49 | + let conn = connect_db()?; |
| 50 | + let adapter = PostgresAdapter::with_metadata_table(&conn, "database_versions"); |
| 51 | + adapter.setup_schema()?; |
| 52 | + |
| 53 | + let mut migrator = Migrator::new(adapter); |
| 54 | + |
| 55 | + let migrations: Vec<Box<PostgresMigration>> = vec![ |
| 56 | + migration!( |
| 57 | + // version |
| 58 | + 1, |
| 59 | + // description |
| 60 | + "Initial database schema", |
| 61 | + // upgrade query |
| 62 | + "CREATE TABLE crates ( |
| 63 | + id SERIAL PRIMARY KEY, |
| 64 | + name VARCHAR(255) UNIQUE NOT NULL, |
| 65 | + latest_version_id INT DEFAULT 0, |
| 66 | + versions JSON DEFAULT '[]', |
| 67 | + downloads_total INT DEFAULT 0, |
| 68 | + github_description VARCHAR(1024), |
| 69 | + github_stars INT DEFAULT 0, |
| 70 | + github_forks INT DEFAULT 0, |
| 71 | + github_issues INT DEFAULT 0, |
| 72 | + github_last_commit TIMESTAMP, |
| 73 | + github_last_update TIMESTAMP, |
| 74 | + content tsvector |
| 75 | + ); |
| 76 | + CREATE TABLE releases ( |
| 77 | + id SERIAL PRIMARY KEY, |
| 78 | + crate_id INT NOT NULL REFERENCES crates(id), |
| 79 | + version VARCHAR(100), |
| 80 | + release_time TIMESTAMP, |
| 81 | + dependencies JSON, |
| 82 | + target_name VARCHAR(255), |
| 83 | + yanked BOOL DEFAULT FALSE, |
| 84 | + is_library BOOL DEFAULT TRUE, |
| 85 | + build_status BOOL DEFAULT FALSE, |
| 86 | + rustdoc_status BOOL DEFAULT FALSE, |
| 87 | + test_status BOOL DEFAULT FALSE, |
| 88 | + license VARCHAR(100), |
| 89 | + repository_url VARCHAR(255), |
| 90 | + homepage_url VARCHAR(255), |
| 91 | + documentation_url VARCHAR(255), |
| 92 | + description VARCHAR(1024), |
| 93 | + description_long VARCHAR(51200), |
| 94 | + readme VARCHAR(51200), |
| 95 | + authors JSON, |
| 96 | + keywords JSON, |
| 97 | + have_examples BOOL DEFAULT FALSE, |
| 98 | + downloads INT DEFAULT 0, |
| 99 | + files JSON, |
| 100 | + doc_targets JSON DEFAULT '[]', |
| 101 | + doc_rustc_version VARCHAR(100) NOT NULL, |
| 102 | + default_target VARCHAR(100), |
| 103 | + UNIQUE (crate_id, version) |
| 104 | + ); |
| 105 | + CREATE TABLE authors ( |
| 106 | + id SERIAL PRIMARY KEY, |
| 107 | + name VARCHAR(255), |
| 108 | + email VARCHAR(255), |
| 109 | + slug VARCHAR(255) UNIQUE NOT NULL |
| 110 | + ); |
| 111 | + CREATE TABLE author_rels ( |
| 112 | + rid INT REFERENCES releases(id), |
| 113 | + aid INT REFERENCES authors(id), |
| 114 | + UNIQUE(rid, aid) |
| 115 | + ); |
| 116 | + CREATE TABLE keywords ( |
| 117 | + id SERIAL PRIMARY KEY, |
| 118 | + name VARCHAR(255), |
| 119 | + slug VARCHAR(255) NOT NULL UNIQUE |
| 120 | + ); |
| 121 | + CREATE TABLE keyword_rels ( |
| 122 | + rid INT REFERENCES releases(id), |
| 123 | + kid INT REFERENCES keywords(id), |
| 124 | + UNIQUE(rid, kid) |
| 125 | + ); |
| 126 | + CREATE TABLE owners ( |
| 127 | + id SERIAL PRIMARY KEY, |
| 128 | + login VARCHAR(255) NOT NULL UNIQUE, |
| 129 | + avatar VARCHAR(255), |
| 130 | + name VARCHAR(255), |
| 131 | + email VARCHAR(255) |
| 132 | + ); |
| 133 | + CREATE TABLE owner_rels ( |
| 134 | + cid INT REFERENCES releases(id), |
| 135 | + oid INT REFERENCES owners(id), |
| 136 | + UNIQUE(cid, oid) |
| 137 | + ); |
| 138 | + CREATE TABLE builds ( |
| 139 | + id SERIAL, |
| 140 | + rid INT NOT NULL REFERENCES releases(id), |
| 141 | + rustc_version VARCHAR(100) NOT NULL, |
| 142 | + cratesfyi_version VARCHAR(100) NOT NULL, |
| 143 | + build_status BOOL NOT NULL, |
| 144 | + build_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| 145 | + output TEXT |
| 146 | + ); |
| 147 | + CREATE TABLE queue ( |
| 148 | + id SERIAL, |
| 149 | + name VARCHAR(255), |
| 150 | + version VARCHAR(100), |
| 151 | + attempt INT DEFAULT 0, |
| 152 | + date_added TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| 153 | + UNIQUE(name, version) |
| 154 | + ); |
| 155 | + CREATE TABLE files ( |
| 156 | + path VARCHAR(4096) NOT NULL PRIMARY KEY, |
| 157 | + mime VARCHAR(100) NOT NULL, |
| 158 | + date_added TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| 159 | + date_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| 160 | + content BYTEA |
| 161 | + ); |
| 162 | + CREATE TABLE config ( |
| 163 | + name VARCHAR(100) NOT NULL PRIMARY KEY, |
| 164 | + value JSON NOT NULL |
| 165 | + ); |
| 166 | + CREATE INDEX ON releases (release_time DESC); |
| 167 | + CREATE INDEX content_idx ON crates USING gin(content);", |
| 168 | + // downgrade query |
| 169 | + "DROP TABLE authors, author_rels, keyword_rels, keywords, owner_rels, |
| 170 | + owners, releases, crates, builds, queue, files, config;" |
| 171 | + ), |
| 172 | + ]; |
| 173 | + |
| 174 | + for migration in migrations { |
| 175 | + migrator.register(migration); |
| 176 | + } |
| 177 | + |
| 178 | + if let Some(version) = version { |
| 179 | + if version > migrator.current_version()?.unwrap_or(0) { |
| 180 | + migrator.up(Some(version))?; |
| 181 | + } else { |
| 182 | + migrator.down(Some(version))?; |
| 183 | + } |
| 184 | + } else { |
| 185 | + migrator.up(version)?; |
| 186 | + } |
| 187 | + |
| 188 | + Ok(()) |
| 189 | +} |
0 commit comments