Skip to content

Commit b564d72

Browse files
authored
Merge pull request #305 from onur/schemamamamama
Add database migration
2 parents 67b2b94 + fefb6b4 commit b564d72

File tree

8 files changed

+229
-141
lines changed

8 files changed

+229
-141
lines changed

Cargo.lock

+22-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ comrak = { version = "0.3", default-features = false }
3030
toml = "0.4"
3131
html5ever = "0.22"
3232
cargo = { git = "https://github.com/rust-lang/cargo.git" }
33+
schemamama = "0.3"
34+
schemamama_postgres = "0.2"
35+
3336

3437
# iron dependencies
3538
iron = "0.5"

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ cargo run -- build world
108108
#### `database` subcommand
109109

110110
```sh
111-
# Initializes database. Currently, only creates tables in database.
112-
cargo run -- database init
111+
# Migrate database to recent version
112+
cargo run -- database migrate
113113

114114

115115
# Adds a directory into database to serve with `staticfile` crate.

Vagrantfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ EOF
120120
echo 'DROP DATABASE cratesfyi; CREATE DATABASE cratesfyi OWNER cratesfyi' | sudo -u postgres psql
121121
122122
############################################################
123-
# Initializing database scheme #
123+
# Migrate database to recent version #
124124
############################################################
125-
su - cratesfyi -c "cd /vagrant && cargo run -- database init"
125+
su - cratesfyi -c "cd /vagrant && cargo run -- database migrate"
126126
127127
############################################################
128128
# Add essential files for downloaded nigthly #

src/bin/cratesfyi.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,9 @@ pub fn main() {
109109
.subcommand(SubCommand::with_name("daemon").about("Starts cratesfyi daemon"))
110110
.subcommand(SubCommand::with_name("database")
111111
.about("Database operations")
112-
.subcommand(SubCommand::with_name("init").about("Initialize database. Currently \
113-
only creates tables in database."))
112+
.subcommand(SubCommand::with_name("migrate")
113+
.about("Run database migrations")
114+
.arg(Arg::with_name("VERSION")))
114115
.subcommand(SubCommand::with_name("update-github-fields")
115116
.about("Updates github stats for crates."))
116117
.subcommand(SubCommand::with_name("add-directory")
@@ -204,9 +205,10 @@ pub fn main() {
204205
}
205206

206207
} else if let Some(matches) = matches.subcommand_matches("database") {
207-
if let Some(_) = matches.subcommand_matches("init") {
208-
let conn = db::connect_db().unwrap();
209-
db::create_tables(&conn).expect("Failed to initialize database");
208+
if let Some(matches) = matches.subcommand_matches("migrate") {
209+
let version = matches.value_of("VERSION").map(|v| v.parse::<i64>()
210+
.expect("Version should be an integer"));
211+
db::migrate(version).expect("Failed to run database migrations");
210212
} else if let Some(_) = matches.subcommand_matches("update-github-fields") {
211213
cratesfyi::utils::github_updater().expect("Failed to update github fields");
212214
} else if let Some(matches) = matches.subcommand_matches("add-directory") {

src/db/migrate.rs

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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

Comments
 (0)