//! Schema bootstrap and migration runner.
//!
//! Migrations are an ordered list of `( version, sql )` pairs. The last
//! applied version is recorded in `meta.schema_version`. A migration that has
//! already been applied is skipped. Once a migration is shipped we never edit
//! it; future changes get a new entry with a new version number.

use rusqlite::{ Connection, OptionalExtension };

use crate::db::DbResult;

const MIGRATIONS: &[ ( i64, &str ) ] = &[
	( 1, V1_INITIAL ),
];

pub fn apply_migrations( conn: &mut Connection ) -> DbResult<()>
{
	conn.execute_batch(
		"CREATE TABLE IF NOT EXISTS meta (
			key   TEXT PRIMARY KEY,
			value TEXT NOT NULL
		);"
	)?;

	let current = current_schema_version( conn )?;
	for ( version, sql ) in MIGRATIONS
	{
		if *version > current
		{
			apply_one( conn, *version, sql )?;
		}
	}
	Ok( () )
}

fn current_schema_version( conn: &Connection ) -> DbResult<i64>
{
	let raw: Option<String> = conn.query_row(
		"SELECT value FROM meta WHERE key = 'schema_version'",
		[],
		|row| row.get( 0 ),
	).optional()?;
	Ok( raw.and_then( |s| s.parse::<i64>().ok() ).unwrap_or( 0 ) )
}

fn apply_one( conn: &mut Connection, version: i64, sql: &str ) -> DbResult<()>
{
	let tx = conn.transaction()?;
	tx.execute_batch( sql )?;
	tx.execute(
		"INSERT INTO meta( key, value ) VALUES( 'schema_version', ?1 )
		 ON CONFLICT( key ) DO UPDATE SET value = excluded.value",
		[ version.to_string() ],
	)?;
	tx.commit()?;
	Ok( () )
}

const V1_INITIAL: &str = r#"
-- v1 initial schema. Subset of Android CallLog.Calls; see docs/schema.md.
CREATE TABLE calls
(
	_id         INTEGER PRIMARY KEY AUTOINCREMENT,
	number      TEXT    NOT NULL,
	date        INTEGER NOT NULL,
	duration    INTEGER NOT NULL DEFAULT 0,
	type        INTEGER NOT NULL,
	new         INTEGER NOT NULL DEFAULT 1,
	cached_name TEXT,
	created_at  INTEGER NOT NULL
);

CREATE INDEX idx_calls_date ON calls( date DESC );
CREATE INDEX idx_calls_new  ON calls( new ) WHERE new = 1;
"#;
