1use std::error::Error;
2
3use diesel::{sql_query, RunQueryDsl, SqliteConnection};
4use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
5
6pub const CORE_MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/core");
7pub const METADATA_MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/metadata");
8
9#[derive(Clone, Copy, Debug)]
10pub enum DbType {
11 Core,
12 Metadata,
13}
14
15fn get_migrations(db_type: &DbType) -> EmbeddedMigrations {
16 match db_type {
17 DbType::Core => CORE_MIGRATIONS,
18 DbType::Metadata => METADATA_MIGRATIONS,
19 }
20}
21
22pub fn apply_migrations(
23 conn: &mut SqliteConnection,
24 db_type: &DbType,
25) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
26 loop {
27 match conn.run_pending_migrations(get_migrations(db_type)) {
28 Ok(_) => break,
29 Err(e) if e.to_string().contains("already exists") => {
30 mark_first_pending(conn, db_type)?;
31 }
32 Err(e) => return Err(e),
33 }
34 }
35
36 Ok(())
37}
38
39fn mark_first_pending(
40 conn: &mut SqliteConnection,
41 db_type: &DbType,
42) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
43 let pending = conn.pending_migrations(get_migrations(db_type))?;
44 if let Some(first) = pending.first() {
45 sql_query("INSERT INTO __diesel_schema_migrations (version) VALUES (?1)")
46 .bind::<diesel::sql_types::Text, _>(first.name().version())
47 .execute(conn)?;
48 }
49
50 Ok(())
51}
52
53pub fn migrate_json_to_jsonb(
71 conn: &mut SqliteConnection,
72 db_type: DbType,
73) -> Result<usize, Box<dyn Error + Send + Sync + 'static>> {
74 let json_condition = |col: &str| {
77 format!(
78 "{col} IS NOT NULL AND (typeof({col}) = 'text' OR (typeof({col}) = 'blob' AND hex(substr({col}, 1, 1)) IN ('5B', '7B')))"
79 )
80 };
81
82 let queries: Vec<String> = match db_type {
83 DbType::Core => {
84 vec![
85 format!(
86 "UPDATE packages SET provides = jsonb(provides) WHERE {}",
87 json_condition("provides")
88 ),
89 format!(
90 "UPDATE packages SET install_patterns = jsonb(install_patterns) WHERE {}",
91 json_condition("install_patterns")
92 ),
93 ]
94 }
95 DbType::Metadata => {
96 vec![
97 format!(
98 "UPDATE packages SET licenses = jsonb(licenses) WHERE {}",
99 json_condition("licenses")
100 ),
101 format!(
102 "UPDATE packages SET homepages = jsonb(homepages) WHERE {}",
103 json_condition("homepages")
104 ),
105 format!(
106 "UPDATE packages SET notes = jsonb(notes) WHERE {}",
107 json_condition("notes")
108 ),
109 format!(
110 "UPDATE packages SET source_urls = jsonb(source_urls) WHERE {}",
111 json_condition("source_urls")
112 ),
113 format!(
114 "UPDATE packages SET tags = jsonb(tags) WHERE {}",
115 json_condition("tags")
116 ),
117 format!(
118 "UPDATE packages SET categories = jsonb(categories) WHERE {}",
119 json_condition("categories")
120 ),
121 format!(
122 "UPDATE packages SET provides = jsonb(provides) WHERE {}",
123 json_condition("provides")
124 ),
125 format!(
126 "UPDATE packages SET snapshots = jsonb(snapshots) WHERE {}",
127 json_condition("snapshots")
128 ),
129 format!(
130 "UPDATE packages SET replaces = jsonb(replaces) WHERE {}",
131 json_condition("replaces")
132 ),
133 ]
134 }
135 };
136
137 let mut total = 0;
138 for query in queries {
139 total += sql_query(&query).execute(conn)?;
140 }
141
142 Ok(total)
143}