From 07bfc65b6754d39e117f50e54b1e00ef24192d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kha=C3=AFs=20COLIN?= Date: Fri, 24 Oct 2025 23:26:26 +0200 Subject: [PATCH] new data format with more information --- foods.db | Bin 8192 -> 12288 bytes src/get_foods.sql | 2 ++ src/get_version.sql | 1 + src/main.rs | 50 +++++++++++++++++++++++++++++++++++++++++-- src/migrations/1.sql | 6 ++++++ src/migrations/2.sql | 23 ++++++++++++++++++++ src/migrations/3.sql | 6 ++++++ src/migrations/4.sql | 23 ++++++++++++++++++++ 8 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 src/get_version.sql create mode 100644 src/migrations/1.sql create mode 100644 src/migrations/2.sql create mode 100644 src/migrations/3.sql create mode 100644 src/migrations/4.sql diff --git a/foods.db b/foods.db index c8e49e5ecb512dec93feab9e8697a967629394df..81ff588f777d56554ae5df3a96484e5c93a53a99 100644 GIT binary patch literal 12288 zcmWFz^vNtqRY=P(%1ta$FlG>7U}R))P*7lCV31&7U|?ooU|?ckU|?imU=UznU|?ZD z0FW3mA0dNaNigVD74m`%;N@oE<>vf|kQ!x;hQMeDjE2By2#kinXb6mkz-S22B?Q_e zS=hz(^%=VpOA?cEQi}_7lHyBJa|`0Z+;~F_0cPhQSH}=ng%C$4A6Eq>40$C54K7Zl z%oHU~PrndXch?{tE>5L_{GyV~{5&Pj5Z8zh1wa1~1;0=qACOF5Vs0uH;q2u2g4Ck; z;?$zD%)E5CS#YfiF0O8lp*|rB1|UO95{uGPOJJ&t@hCS0DNjr;DNW2FMmgB@!A`+I6Kqydeo1O( zo)Tw}tD_I5uTAw3CO}mx*x5q$re!9j77?Knq7I?20Adn2;Gup&H^tr6&o#&~#MMQ? z(I?z7GFZVeSV2QW393^`L0`epz(7+$O99c%xXIk&c2# zeqLs}j)HGuQEqBZVoItAOgBt3jAj)v;Nf8q7eyH2Tv`yEm{t;iO9#saQ*IsxX>}$} zr~~zl6r4*7+;UPgi<2|--9RM63FS1i)Rg3u6cLz3%q;<2JPe}2jEs!Zj8NCQrIwT^ zXgHOYlz`mqS6W=01Z5wdmZOkblB}nx!OR3R3T6_FW`5zx$-^M+$jHb6F;U+HB zi$6OLgLoh#Bk^Vyc?ufgrA1Y# zIeDNokXa1&zGeW-WCzBXATY~;6-3RP#i--Y3UMklBthPH%1JCKNr(7WK_fUZC$R)% zSYCR5P71_4E)1vI|A6FQrU+dY9tK%aNYpSg87esIgOag-QgKNDLf2)8$C#M*=rQv! zh{`i^g3<;^xvP&)YGz(>Vs2`l9J8`nibaY=VhSv_SXnE4n0OeZU75h@*_jL>F&~g$ zkepwXr=w7mpP`^(YnWVGpx~ZbQk0pJs>yJI4Q7mk&>0Xo>mUVjEW-(RMji%fTWHE; zgqjA@nVOlBs_$2t0uD%Mgr_hw!A+8cgr9>Tgihh*$!Fl>{>{L@oj;5B0l5Dk$Nig+ zlee7LjOQKC8J_7pzEDd?snHM^4S~@R7!85Z5Eu=C(GVC7fzc2c4S}H>0s+v@9;gQc z>We#<77*w_f;xLlOt3x~W^YdcX&it9%Qygw53ekP1~DOKj2I{20GUL@I037O0%BYM ztO?ZFOhg(NVA-IHG!DSQWQczpfVqWt7lR~L*V;n{1~?d{8NoJ%_z*KN03R1%e&L8T z4ghipp>Y5<^l<=Y1%68gHLQ+95mbkCQ$a%*92nhHct4euwIUcYoIpa?AKJS#Vu2+H z%<+UsNcSJv&|p%!|M0Xn`1=103=D=SAx(he;|(1EfDTv?GXMbZwNu#tXM#tx1A6}- E0CH62hX4Qo delta 593 zcmZojXmFSy&C0~Uz`!)oK$?+pVxclW6N6q=Auj_110&xt2EJpP4FyDaHa560)i;?l zvWts~GPVSkBqrsgrse0SfH0GDkgH>et3rsQlaH$cNJK${OF=f-~E%1g{m#U{?5og80~S`=TL zT2z*qm##3`o=;NT0HUWPu_!&Y1SVAsl9LjHNF^qhlqTk&NJ;WSwI%206eJ^IqWH!rR810}T@sMimJ@ z4hBgpE=w*B2GL+fMn>t$+OmdRMw}cB(vFOb9MY3(Wwp4h zIXD=^BN;h4r6Vs2`l s9J8`nibaY=qA3#xgS0CXC!_S_3VqedbL2U=3>i5Xq;0{DJu0sW0H30Ui~s-t diff --git a/src/get_foods.sql b/src/get_foods.sql index dbd3e94..69523a1 100644 --- a/src/get_foods.sql +++ b/src/get_foods.sql @@ -8,3 +8,5 @@ SELECT color FROM food +ORDER BY + sort_order, name; diff --git a/src/get_version.sql b/src/get_version.sql new file mode 100644 index 0000000..4edeca1 --- /dev/null +++ b/src/get_version.sql @@ -0,0 +1 @@ +PRAGMA user_version; diff --git a/src/main.rs b/src/main.rs index d1d1eb7..3ab24fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use axum::{ routing::{get, post}, }; use parking_lot::Mutex; -use rusqlite::{CachedStatement, Connection, Row}; +use rusqlite::{CachedStatement, Connection, Row, Transaction}; use tower_http::trace::TraceLayer; use tower_request_id::{RequestId, RequestIdLayer}; use tracing::{debug, error, info, info_span}; @@ -123,6 +123,50 @@ impl<'conn> PreparedStatements { type ConnState = Arc>; +fn get_version(tx: &Transaction) -> rusqlite::Result { + tx.query_one(include_str!("get_version.sql"), (), |row| row.get(0)) +} + +fn get_migrations() -> [&'static str; 4] { + [ + include_str!("migrations/1.sql"), + include_str!("migrations/2.sql"), + include_str!("migrations/3.sql"), + include_str!("migrations/4.sql"), + ] +} + +fn do_migrations(conn: &mut Connection) -> rusqlite::Result<()> { + let migrations = get_migrations(); + let num_migrations = migrations.len(); + let tx = conn.transaction()?; + let version = get_version(&tx)?; + if version < migrations.len() { + info!( + migrations_to_apply = num_migrations - version, + "need to apply some migrations" + ); + let mut mig_number = version; + for migration in migrations.iter().skip(version) { + mig_number += 1; + info!(mig_number, "applying migration"); + debug!(migration = migration); + tx.execute_batch(migration)?; + if get_version(&tx)? != mig_number { + panic!( + "expected user_version to eq {} after applying migration {}. maybe a missing 'PRAGMA user_version =' ?", + mig_number, mig_number + ); + } + } + tx.commit()?; + info!("applied all migrations"); + } else { + info!("no migrations to apply"); + } + Ok(()) +} + #[tokio::main] async fn main() -> Result<(), std::io::Error> { tracing_subscriber::registry() @@ -142,13 +186,15 @@ async fn main() -> Result<(), std::io::Error> { let db_connecion_str = "./foods.db".to_string(); debug!(db_connecion_str, "opening database"); - let conn = Connection::open(db_connecion_str).expect("failed to open database"); + let mut conn = Connection::open(db_connecion_str).expect("failed to open database"); if let Err(e) = conn.execute(include_str!("create_tables.sql"), ()) { error!(?e, "failed to create tables"); panic!("failed to create tables: {:#?}", e); } + do_migrations(&mut conn).expect("failed to do database migrations"); + PreparedStatements::check(&conn).expect("failed to prepare sql statements"); let app = Router::new() diff --git a/src/migrations/1.sql b/src/migrations/1.sql new file mode 100644 index 0000000..b5d5cff --- /dev/null +++ b/src/migrations/1.sql @@ -0,0 +1,6 @@ +ALTER TABLE + food +ADD COLUMN + portion_weight INTEGER NOT NULL DEFAULT 100 CHECK (portion_weight > 0); + +PRAGMA user_version = 1; diff --git a/src/migrations/2.sql b/src/migrations/2.sql new file mode 100644 index 0000000..755c45c --- /dev/null +++ b/src/migrations/2.sql @@ -0,0 +1,23 @@ +ALTER TABLE + food +ADD COLUMN + -- per 100g + protein REAL NOT NULL DEFAULT 5.0 CHECK (protein > 0); + +ALTER TABLE + food +ADD COLUMN + -- per 100g + fiber REAL NOT NULL DEFAULT 5.0 CHECK (fiber > 0); + +ALTER TABLE + food +ADD COLUMN + protein_per_portion REAL NOT NULL GENERATED ALWAYS AS ((protein / 100) * portion_weight) VIRTUAL; + +ALTER TABLE + food +ADD COLUMN + fiber_per_portion REAL NOT NULL GENERATED ALWAYS AS ((fiber / 100) * portion_weight) VIRTUAL; + +PRAGMA user_version = 2; diff --git a/src/migrations/3.sql b/src/migrations/3.sql new file mode 100644 index 0000000..46c6d51 --- /dev/null +++ b/src/migrations/3.sql @@ -0,0 +1,6 @@ +ALTER TABLE + food +ADD COLUMN + sort_order INTEGER NOT NULL DEFAULT 0; + +PRAGMA user_version = 3; diff --git a/src/migrations/4.sql b/src/migrations/4.sql new file mode 100644 index 0000000..c6d9caa --- /dev/null +++ b/src/migrations/4.sql @@ -0,0 +1,23 @@ +CREATE TABLE "sqlb_temp_table_1" ( + "id" INTEGER, + "portion" TEXT NOT NULL, + "name" TEXT NOT NULL, + "kc_per_serving" INTEGER NOT NULL DEFAULT 0, + "target_servings" INTEGER NOT NULL DEFAULT 1, + "actual_servings" INTEGER NOT NULL DEFAULT 0, + "color" TEXT NOT NULL DEFAULT 'white', + "portion_weight" INTEGER NOT NULL DEFAULT 100 CHECK("portion_weight" > 0), + "protein" REAL NOT NULL DEFAULT 5.0 CHECK("protein" >= 0), + "fiber" REAL NOT NULL DEFAULT 5.0 CHECK("fiber" >= 0), + "protein_per_portion" REAL NOT NULL GENERATED ALWAYS AS (("protein" / 100) * "portion_weight") VIRTUAL, + "fiber_per_portion" REAL NOT NULL GENERATED ALWAYS AS (("fiber" / 100) * "portion_weight") VIRTUAL, + "sort_order" INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY("id") +) STRICT; +INSERT INTO "main"."sqlb_temp_table_1" ("actual_servings","color","fiber","id","kc_per_serving","name","portion","portion_weight","protein","sort_order","target_servings") SELECT "actual_servings","color","fiber","id","kc_per_serving","name","portion","portion_weight","protein","sort_order","target_servings" FROM "main"."food"; +PRAGMA defer_foreign_keys = '1'; +DROP TABLE "main"."food"; +ALTER TABLE "main"."sqlb_temp_table_1" RENAME TO "food"; +PRAGMA defer_foreign_keys = '0'; + +PRAGMA user_version = 4;