From fead8685b6ae0f25ff4fb99acf0d2a544f97380e Mon Sep 17 00:00:00 2001 From: Filipriec Date: Sat, 29 Aug 2026 08:15:49 +0200 Subject: [PATCH] table revision number --- client | 2 +- common/proto/table_structure.proto | 2 ++ common/proto/tables_data.proto | 3 +++ common/src/proto/descriptor.bin | Bin 200698 -> 201108 bytes common/src/proto/komp_ac.table_structure.rs | 2 ++ common/src/proto/komp_ac.tables_data.rs | 4 ++++ server | 2 +- web/src/pages/import_export/import/logic.rs | 22 ++++++++++++++++++-- 8 files changed, 33 insertions(+), 4 deletions(-) diff --git a/client b/client index bcd298b3..1d3e75b1 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit bcd298b3f1a9930e71f3c65caa3fa4a1361737f5 +Subproject commit 1d3e75b1502956959ae5ea43c1514ecd23904a0b diff --git a/common/proto/table_structure.proto b/common/proto/table_structure.proto index 8d44b06e..7d66a6b1 100644 --- a/common/proto/table_structure.proto +++ b/common/proto/table_structure.proto @@ -143,6 +143,8 @@ message GetTableImportDescriptorResponse { string storage_profile_name = 2; string table_name = 3; int64 table_id = 4; + // Version of this import contract. Send it as expected_table_revision on + // every staged chunk so schema/rule changes refuse stale prepared data. int64 table_revision = 5; repeated ImportFieldDescriptor fields = 6; } diff --git a/common/proto/tables_data.proto b/common/proto/tables_data.proto index a71f3881..e38f047b 100644 --- a/common/proto/tables_data.proto +++ b/common/proto/tables_data.proto @@ -199,6 +199,9 @@ message StageTableDataImportRequest { string import_id = 1; string table_name = 2; repeated TableDataImportRow rows = 3; + // Import-contract revision returned by GetTableImportDescriptor. A zero + // value is accepted for older clients and snapshots the current revision. + int64 expected_table_revision = 4; } message TableDataImportRow { diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 8a984bf5cb1b90dc2b76fcbe70593c16cc40fd00..29713fe95b9afe6146a14f27665a9afddd2ae5c8 100644 GIT binary patch delta 7311 zcmZ{odyrMtm51+H`}XPEx4Y@)bklF>OXCAGc9Vb^okXj~REDV3R5Ciwq^!v#EnEW= z?54UKXPlag=EqM&zm>nbT~19N{?_esxvJ{a#C&B^5oHz+Prh63 zZ;lqHOTR0hm6k>MY_?LeNp@i*?QPFQg}*Bf49@89EA&577@9s~urTA%S^Wb;g{l30 zLj%22hk6PR6#J$XW(*a22Mfi2nN^%RRGjwxq27mQ7Qa7Gd~AmE?aLR6j}-?VFANU# z{;)W$Fm?Luz8@6^`wN3prxzdXoiH$aW|4=zrM@2)2MYtmM`jNe)t0BT28y$K2k@EJ zJJj2g&)4QFG{Fj+VP(KbG1nB(D7LocYiu>xE{fz?K#|HDd|X`=1vf4v~-9m${PoByAZTe5{{yt#i=82(!ojk;pVB-tJ5eaGa^(hkbA{yfBHM|9&Y0SUJwy0zOz zt4!iQMVUQ5S|yf$igGvjwI$lFNLu`AWk?bXZJ|+jMY)DBO4MCZQ?qx~NV_8$lb)F% zqf4#cD8guW)G^vlolD}qQRZDgbqjyD4m}YOO7{&Eq)Y57*$|t4xaFjXfQ_5p{ILeK)Ve^{Nkx(c^SkM&;VtXVM3KI56 zD6~3>k4KpkK58|V$Af&;8uk59XSGb!u&^tXQHE8F{ zrn(QKTzme@bXJWkcr(VLw-CY(8;LEl?@W;q?Ub$bz$&KI`f2lZQ_}-iz;;{ zs&Y$EBia#Xv~4C@7fG_`BdKe8%R z-r)7&ul}!mN4l?v_OG$K>3V2?P_4B6AFw_gSaZ@T7vF8Nl+4e>?ZR3tpA_F^wW`&8k+HTJAPzy$i3DOhUz$3?C{gE2XCYtG^R!kg z{He$kKN{f+_5`$}6D9}5_os62eYTZq1@WFL&m*ik=+>M;T%VH4dhe>zJtYl|-W4h> z5c!H9HK+nQoons!v_PsyhEYN-ki?z&jN-gNCVbg8`-mj|g=Bu|bs2%>FT|fGBUG2C zgDw!YflikZQc_`_4#F@@*r!z()n$aH|4c{_B0qU&g$<=ubq_Ix`mSK$WLa*I?Z-*+ImXv=~*0P6*lEa#|vOD}#2d%IOc{xJ32;lAVjCdoDe8N^b3bF7#iWU9#sS z=Z-lLm*<3I?sn?}Elck@Eq~Fytn8w1j%AW-v@2C-mu8tXx8(1&E{L`w{ncriQd$wT ztFukNLaIYz+Vm@AWV?@`O}|3MblSzOPvYk#^J~Aj^;kYHIk!OdD%T6*&qs*rL3faW z*uEhCpr~i93HyTB12UVfSKU^Lf6+krg27jnTP6O)U=-I?;$JlN+Vn4q)Y)_)TtRnU zfVjR$pfHN-i^6%qUX$#|Bwj6K{JT3psO3i_~oD(NZ6NyWQkU` zHb@5H3IGZ5cbf@UDTuc&6a4HKo`h>WqNLb!s# zRlDyu;lO0n8v6sYA&Fm=%zq`x0W5c{fU1 zyUzmK*UJS|I1H}(wf%ZH3>u^Y*VnaxTk=(sZA{|dOXi*Q%y}7K|9i=evG4gtU5Pg( z9dSbb>^6XaZw5s`0=^kK5+vZ8+L8LYZ&bN9OZw;unKTK&b932K*I{#LNJjD8tPQE( z7LB?LThfyk__o**3g;m%TO{XRIv_4vq;ZseZ8s`y)z|ieJzL8zs`J*+f7qyzzOZj( zw~tEV?UMOpy8WWuP`8~unEwWIkJ5T~q|qh$X3vh$aXci@j?i%+fp%!eeU(5>NxVxk z2h+hza$Ef_@gGP{8fcFf4xs!!p?r{ddxCHv@%9AanzYb;UN`{HePvG-ZeI|NQ9Spl za9<($CKYaf`frzIQfYrsfrq&44;vW7Wxw0Nl5I}nLy|e}kE3QR4@s`eHdC`!a5$Vs z5Q%_pH-Ok44kds@JseJ>W+^dxBpgT0wg@ozY7s|55scz{BpgT0TEx+C8bM48x*i5` zJsOGuaXlJNqh_UJBJ9vRxVVD&!ogSD;h5yyyMa+_oiya%j`C8G;X79{=hNd?q$~KQ`{0z7@+)^_~bSsLz8=txDHLT;&jYGF_L%{c(e_^&O}G3rMytiRYNi zd@oZQmU9f3q?&vwn{88_<{JM!1`!r?I)NC^HU4`HByxI< z^*CsayT;}3LWuSxUSKk7Ow=}^aDV?l7t+VC$)x%Ph8x5=p=sJxjD^F$yC#EC&qCu@ zoTtQGX#9$U#9V0D7;or*Q|+4UlJtAmWm9R1@$17wgj{0$6%14(<97r|h$Y7Fh;~(D zsnH!lk3+09=vEpe($aDyoo=b|8-h_HEj4V2rzD@#Y~bauK5wd{?&U_Df;_}&xfwgb z&!DO-Hxno2|Hc-;d@Ixak$IqJWmx)l(eId*#{XRe;h*gHG$+Ip53%5F*V9b(p6kks5PEs13#KR`@dU`HvzFrzO{`48GnXC_c0U{vi zUauel*N0R<0iUh*+DIFXzXd@k47$H= zKzuivMtAK2@!e?nsAX&LrJ2{gHORw5eQNxr9mHp=;nGeq zxRBDebVklRbnmvZiw4?es@$K5*x<6w*vpD!JCpbiCbP$%oSj(yL7Q!!X3(kG?@Vi| z%>3@1VcYN!qn*aTSU|${=egWtX7FO4WSX}K9N8I?hd;J#C5lGl?qAXy(aTNF8K2MubM&x>sHW1{7Be}5U!x>ZE!5UN5Wo&@jaq@ z@gME9}h&T_k-#_Hi=J|%tu~#4SvG#FWz4hZLI44 zLAtKSe0#Q5W~VbznlOLI8uPUiG%t%-ZihPoIc+}W`3AmPr2gSksHx!@0G0AVhaJvEUF zVci+U^MbDXPjr8F>B3)0e>TF*?Y*+tj+ rYG@H`)F`w_m#*_CVVxnQ0^K@;xPB6r8N~Gymigg$)RRasgn{rCziAQV3AB<8Zc3;D+eUgoVfS& zfF_f|oPigo$`x13)7u9QJ}Y;acwYXa7bG7zYq4w^o4%RHuIyt2HSb7!PrNQK?~(V$ z*QZiQvSfW6y&K1AU1|ymx!1>~&h#Q?lt`2nHspK$BbQV*#B!>=As30+5Rab_keO_V z&z{Ikq)?M3@5Rw(pRI<-_u|s2ev)JxjHTVz9WiGx_@h||<5F`NCF@|^+UBJdMmNPW z&X2}u!Jv)pXsIKNqTLjCkF^z*vSdpfz3(e35xFH)RMPnk#nNVV5Lv-eKw^jD+Dx%h zV3@Q+@t9Vdwl+()#?c2pZ7rc&l66}=I`dKn zx5v`y2X&rcOpdDdmg?l{_Wi%(le=ULOcWw5=q3sh`%#!ENZOBdqNmd* z^;xnvj_%Lz{zOh|+8dX;(sOCJdQG@5&R37fS(5?s?F(gszivIezkrbw@C1&A{ObmoA>&Xm^fFiO^$(o?qG&>*^- z9u&FGkH%=h{(yG$oGAg(J}B2*>$SM`Q1w27(V$!M2J6~GQq$z6>e@rn+~TFsERpll zh6>HxAO=HVXE8{sSyDIJYt_qHlDQ+1Q7b;@TvAD4RF*s<(eJ!3qlkP&{3$a^eR(wa z0x>tx`2rIAs8qZ$FliqR#*ETMf6P`$fe;0Pu0W6i9}5M7HrN*klIby_z%m6kX31=c z7E0WimA`-2Z9lztTKVp2_uMu8&U>cyr)gdO^Z}`9oGqp9bPBD}s5Q^YUppXI_s
XWE;wg7fp=9F()q zpD+FhZL}*lUrP3{BfGBLeBrFSz)GMeMLIes>9|1ZLW#Ny7RYG#>|~S-3uIi6UG1hUc}Ak={c1N6 z`HYlYD>tcg&x$`UA$$Pc!3GlhtoRe7Nh)gLvtrN7LZL}5TqOQ^1fd0kRy|xK{>)$$ z=_2vZqb9+_=R_LqgdwD$vjar>9Erjx(&vP;gVzKpjLwq9620hWGn&xFQtGukWVFs^ ziO3mN2;m&)@_^iBN$5Wi=@Q9K3BlZDiJW%2wJgh$mn2%|Z;}!Dl9YNbws*<&E-weq zAkuU$*_&~kNM^-`LjfS@iT5F^3S13nsvgjiGLnLqy^oX z1rqyO*k&MEU(;=-6*X(Q%SFaolPe%v*p&p@;gWl$0nsj(u5Pczh!yM_t8KPMfX+h@ z=?bZJdj>?hLfA9zw$RGZ2+j7S1S_r+V5(RdIsqir%Fqd*728hG8PG%nBJN25AqB$( zbl(j~$-MzFigZ9ayL=VIzFwU`@jS;tTSWVM*!9g)MEknVU;SH%{#;S0^ug;`W_^9XQ&+{>&|c_x7jz7;czCm+3f}ftxjS%OoCCg!{OX%(@A_7 z&K-!`g05LXq#uUKfJi?K=T4i_E)n+Pg*^B{v~bXB1MHHLJM9^@(OGl)_c)be#G9-{ z2lL9;@=j&16z%3OMry(*BK9p`i4NVj+ZuptBGp_RC-)NMT*A zRNNA(h&6qMAx=Iw6_+>w;jbJw^WzkV0pc zoRny$abGu`M4r@Bwmz*C3Z3fHea63GL8JwpPawhf8UKa_lJ-91-y%A-`umM95W*(V zwGU`VyIe&e()*3ANbibG517jTwSx8m;~(LSqJ6-02CX{rKvfIPF#cbXPBBP9_w)ym z&M=*>$3Yw1cS?FGMs#J#EE6p;apyVZo2LJ&oHw78Nlmj1p9fzEP1B`j%pSPqq}&rv zo^AY!bCsO4jbCw)oU;uZV-@=u5u*3bE*5$fW%Zqz zK4rwY$yHQOncj2!E!3f>%=wej@7l?5!-e@Tjk&phVQ7Oc(YKd{#{XvoB3Wp7s6Fls zpsGdrQDf#_x~N)fQLh^R;n5}f60^wo4-b$Oiwqwg^IQrRadEz`VCMHP4$asl`X||9 z)8sk|M6+0Z*FQG8dGj{WpYq=q&1p?98NO5Wm84r&aH+9>Y~+WECh1>l{8Nn~(k(Uq zD+MIoQsZBLy4A~N#{RD{8KC%ORi!T3GULBd7)7~E=YI)>jLnkeCVD-8G%*)fmK%Tm zjMYL`gxvs<5Ofb%kc2BjDIf_~XeoMhjnz_C8vnfnAq9g}Ygrj;VHD{~twp7mkJbJe zF#gL1!dKA!Ap@cvFfHy=21Gkxc!6beus+FOP4sp?tHxZ?_*YYGO@GMKD%N?tnQy8w zQ^&pOYtoC{=1s#3`1frLY2V7PEtwzmyk*>11u^Dqt6136yM+->0jfO|?*;c~fU_Q0Z^j8LrxId|TY|(?J%{4lR zWKbJjJ?hb-HyQubhVTeD64@xg4qyKu;QL^ zFxnl)f3JXOcNo4`)cms2&R{--`JnqZ1w^{jjCSh=+F-wKWj(ux!&Zclg08p0UbMqu zFT!Ytbua$d-j*77<mpIp7GI!ir6cbQtZAIkO&-KG0MeecbZJtq3Z`>x6N82+le zo@~A9`^Wk9qs$GJkAn|fBNYfv)jwJ{<=Kk2KZx{bSbY%b RQC9!f, + /// Import-contract revision returned by GetTableImportDescriptor. A zero + /// value is accepted for older clients and snapshots the current revision. + #[prost(int64, tag = "4")] + pub expected_table_revision: i64, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct TableDataImportRow { diff --git a/server b/server index e4392276..3b4f7ee7 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit e4392276bcdf31be965f638e84eb8cde746d6cdb +Subproject commit 3b4f7ee71ec316ead162c698f934b15959fcce27 diff --git a/web/src/pages/import_export/import/logic.rs b/web/src/pages/import_export/import/logic.rs index 6c721553..e4cebcdc 100644 --- a/web/src/pages/import_export/import/logic.rs +++ b/web/src/pages/import_export/import/logic.rs @@ -12,7 +12,7 @@ use axum_extra::extract::Form; use crate::{ AppState, definitions::{ - table_structure::GetTableStructureRequest, + table_structure::{GetTableImportDescriptorRequest, GetTableStructureRequest}, tables_data::{ AbortTableDataImportRequest, BeginTableDataImportRequest, CommitTableDataImportRequest, StageTableDataImportRequest, TableDataImportRow, @@ -52,6 +52,7 @@ const CHUNK_ROWS: usize = 1_000; /// a refusal on the next click instead of a value written somewhere else. struct Destination { table_name: String, + table_revision: i64, columns: Vec, types: HashMap, } @@ -258,6 +259,7 @@ pub(crate) async fn import_csv( id: id.clone(), profile_name, table_name: destination.table_name.clone(), + table_revision: destination.table_revision, rows: converted, })); @@ -363,6 +365,7 @@ struct Running { id: String, profile_name: String, table_name: String, + table_revision: i64, rows: Vec, } @@ -390,6 +393,7 @@ async fn run_import(job: Running) { import_id: import_id.clone(), table_name: job.table_name.clone(), rows: chunk.to_vec(), + expected_table_revision: job.table_revision, }; let Ok(request) = authenticated_request(&job.headers, request) else { abort_import(&job, &import_id).await; @@ -522,11 +526,24 @@ async fn destination( )); } + let descriptor_request = GetTableImportDescriptorRequest { + profile_name: profile_name.clone(), + table_name: table_name.clone(), + }; + let mut structures = state.structures.clone(); + let descriptor = structures + .get_table_import_descriptor( + authenticated_request(headers, descriptor_request) + .map_err(|_| Redirect::to("/login").into_response())?, + ) + .await + .map_err(|error| grpc_error(headers, &error))? + .into_inner(); + let request = GetTableStructureRequest { profile_name, table_names: vec![table_name.clone()], }; - let mut structures = state.structures.clone(); let structure = structures .get_table_structure( authenticated_request(headers, request) @@ -552,6 +569,7 @@ async fn destination( } Ok(Destination { table_name, + table_revision: descriptor.table_revision, types: column_types(&structure), columns, })