@@ -8,8 +8,6 @@ use version::version;
8pub struct State {
8pub struct State {
9 pub version: String,
9 pub version: String,
10 pub status: Status,
10 pub status: Status,
11− pub current_migration: Option<String>,
12− pub migrations: Vec<Migration>,
15#[derive(Serialize, Deserialize, Clone, Debug)]
13#[derive(Serialize, Deserialize, Clone, Debug)]
@@ -72,25 +70,25 @@ impl State {
73 let default = Self::default();
71 let default = Self::default();
74 self.status = default.status;
72 self.status = default.status;
75− self.current_migration = default.current_migration;
76− self.migrations = default.migrations;
81 // Complete will change the status from Completing to Idle
77 // Complete will change the status from Completing to Idle
82− pub fn complete(&mut self) -> anyhow::Result<()> {
78+ pub fn complete(&mut self, db: &mut impl Conn) -> anyhow::Result<()> {
83 let current_status = std::mem::replace(&mut self.status, Status::Idle);
79 let current_status = std::mem::replace(&mut self.status, Status::Idle);
85 match current_status {
80 match current_status {
86− Status::Completing { mut migrations, .. } => {
81+ Status::Completing { migrations, .. } => {
87− let target_migration = migrations.last().unwrap().name.to_string();
82+ // Add migrations and update state in a transaction to ensure atomicity
88− self.migrations.append(&mut migrations);
83+ let mut transaction = db.transaction()?;
89− self.current_migration = Some(target_migration);
84+ save_migrations(&mut transaction, &migrations)?;
85+ self.save(&mut transaction)?;
86+ transaction.commit()?;
92 // Move old status back
89 // Move old status back
93 self.status = current_status;
90 self.status = current_status;
94 return Err(anyhow!(
92 return Err(anyhow!(
95 "couldn't update state to be completed, not in Completing state"
93 "couldn't update state to be completed, not in Completing state"
@@ -138,35 +136,24 @@ impl State {
141− pub fn get_remaining_migrations(
143− new_migrations: impl IntoIterator<Item = Migration>,
144− ) -> anyhow::Result<Vec<Migration>> {
145− let mut new_iter = new_migrations.into_iter();
147− // Ensure the new migration match up with the existing ones
148− for pair in self.migrations.iter().zip(new_iter.by_ref()) {
149− let (existing, ref new) = pair;
150− if existing != new {
152− "existing migration {} does not match new migration {}",
159− let items: Vec<Migration> = new_iter.collect();
161− // Return the remaining migrations
165 fn ensure_schema_and_table(db: &mut impl Conn) {
139 fn ensure_schema_and_table(db: &mut impl Conn) {
166 db.run("CREATE SCHEMA IF NOT EXISTS reshape").unwrap();
140 db.run("CREATE SCHEMA IF NOT EXISTS reshape").unwrap();
168 db.run("CREATE TABLE IF NOT EXISTS reshape.data (key TEXT PRIMARY KEY, value JSONB)")
142 db.run("CREATE TABLE IF NOT EXISTS reshape.data (key TEXT PRIMARY KEY, value JSONB)")
169 .unwrap();
143 .unwrap();
147+ CREATE TABLE IF NOT EXISTS reshape.migrations (
148+ index INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
151+ actions JSONB NOT NULL,
152+ completed_at TIMESTAMP DEFAULT NOW()
@@ -175,8 +162,107 @@ impl Default for State {
176 version: version!().to_string(),
163 version: version!().to_string(),
177 status: Status::Idle,
164 status: Status::Idle,
178− current_migration: None,
169+pub fn current_migration(db: &mut dyn Conn) -> anyhow::Result<Option<String>> {
170+ let name: Option<String> = db
174+ FROM reshape.migrations
180+ .map(|row| row.get("name"));
184+pub fn remaining_migrations(
186+ new_migrations: impl IntoIterator<Item = Migration>,
187+) -> anyhow::Result<Vec<Migration>> {
188+ let mut new_iter = new_migrations.into_iter();
190+ // Ensure the new migrations match up with the existing ones
191+ let mut highest_index: Option<i32> = None;
193+ let migrations = get_migrations(db, highest_index)?;
194+ if migrations.is_empty() {
198+ for (index, existing) in migrations {
199+ highest_index = Some(index);
201+ let new = match new_iter.next() {
202+ Some(migration) => migration,
205+ "existing migration {} doesn't exist in local migrations",
211+ if existing != new.name {
213+ "existing migration {} does not match new migration {}",
221+ // Return the remaining migrations
222+ let items: Vec<Migration> = new_iter.collect();
228+ index_larger_than: Option<i32>,
229+) -> anyhow::Result<Vec<(i32, String)>> {
230+ let rows = if let Some(index_larger_than) = index_larger_than {
231+ db.query_with_params(
234+ FROM reshape.migrations
239+ &[&index_larger_than],
245+ FROM reshape.migrations
251+ let migrations = rows
253+ .map(|row| (row.get("index"), row.get("name")))
258+fn save_migrations(db: &mut impl Conn, migrations: &[Migration]) -> anyhow::Result<()> {
259+ for migration in migrations {
260+ let encoded_actions = serde_json::to_value(&migration.actions)?;
261+ db.query_with_params(
262+ "INSERT INTO reshape.migrations(name, description, actions) VALUES ($1, $2, $3)",
263+ &[&migration.name, &migration.description, &encoded_actions],