1+ use core:: fmt:: from_fn;
2+
13use alloc:: {
4+ format,
25 string:: { String , ToString } ,
36 vec,
47 vec:: Vec ,
58} ;
69use powersync_sqlite_nostd:: { Connection , Destructor , ResultCode } ;
710
8- use crate :: error:: PowerSyncError ;
11+ use crate :: {
12+ error:: PowerSyncError ,
13+ schema:: { RawTable , SchemaTable } ,
14+ utils:: { InsertIntoCrud , SqlBuffer , WriteType } ,
15+ views:: table_columns_to_json_object,
16+ } ;
917
1018pub struct InferredTableStructure {
11- pub table_name : String ,
12- pub has_id_column : bool ,
1319 pub columns : Vec < String > ,
1420}
1521
@@ -35,12 +41,112 @@ impl InferredTableStructure {
3541
3642 if !has_id_column && columns. is_empty ( ) {
3743 Ok ( None )
44+ } else if !has_id_column {
45+ Err ( PowerSyncError :: argument_error ( format ! (
46+ "Table {table_name} has no id column."
47+ ) ) )
48+ } else {
49+ Ok ( Some ( Self { columns } ) )
50+ }
51+ }
52+ }
53+
54+ /// Generates a `CREATE TRIGGER` statement to capture writes on raw tables and to forward them to
55+ /// ps-crud.
56+ pub fn generate_raw_table_trigger (
57+ db : impl Connection ,
58+ table : & RawTable ,
59+ trigger_name : & str ,
60+ write : WriteType ,
61+ ) -> Result < String , PowerSyncError > {
62+ let Some ( local_table_name) = table. schema . table_name . as_ref ( ) else {
63+ return Err ( PowerSyncError :: argument_error ( "Table has no local name" ) ) ;
64+ } ;
65+
66+ let Some ( resolved_table) = InferredTableStructure :: read_from_database ( local_table_name, db) ?
67+ else {
68+ return Err ( PowerSyncError :: argument_error ( format ! (
69+ "Could not find {} in local schema" ,
70+ local_table_name
71+ ) ) ) ;
72+ } ;
73+
74+ let as_schema_table = SchemaTable :: Raw {
75+ definition : table,
76+ schema : & resolved_table,
77+ } ;
78+
79+ let mut buffer = SqlBuffer :: new ( ) ;
80+ buffer. create_trigger ( "" , trigger_name) ;
81+ buffer. trigger_after ( write, local_table_name) ;
82+ // Skip the trigger for writes during sync_local, these aren't crud writes.
83+ buffer. push_str ( "WHEN NOT powersync_in_sync_operation() BEGIN\n " ) ;
84+
85+ if table. schema . options . flags . insert_only ( ) {
86+ if write != WriteType :: Insert {
87+ // Prevent illegal writes to a table marked as insert-only by raising errors here.
88+ buffer. push_str ( "SELECT RAISE(FAIL, 'Unexpected update on insert-only table');\n " ) ;
3889 } else {
39- Ok ( Some ( Self {
40- table_name : table_name. to_string ( ) ,
41- has_id_column,
42- columns,
43- } ) )
90+ // Write directly to powersync_crud_ to skip writing the $local bucket for insert-only
91+ // tables.
92+ let fragment = table_columns_to_json_object ( "NEW" , & as_schema_table) ?;
93+ buffer. powersync_crud_manual_put ( & table. name , & fragment) ;
94+ }
95+ } else {
96+ if write == WriteType :: Update {
97+ // Updates must not change the id.
98+ buffer. check_id_not_changed ( ) ;
4499 }
100+
101+ let json_fragment_new = table_columns_to_json_object ( "NEW" , & as_schema_table) ?;
102+ let json_fragment_old = if write == WriteType :: Update {
103+ Some ( table_columns_to_json_object ( "OLD" , & as_schema_table) ?)
104+ } else {
105+ None
106+ } ;
107+
108+ buffer. insert_into_powersync_crud ( InsertIntoCrud {
109+ op : write,
110+ table : & as_schema_table,
111+ id_expr : from_fn ( |f| {
112+ if write == WriteType :: Delete {
113+ f. write_str ( "OLD." )
114+ } else {
115+ f. write_str ( "NEW." )
116+ } ?;
117+ f. write_str ( ".id" )
118+ } ) ,
119+ type_name : & table. name ,
120+ data : Some ( from_fn ( |f| {
121+ match write {
122+ WriteType :: Insert => { }
123+ WriteType :: Update => todo ! ( ) ,
124+ WriteType :: Delete => {
125+ // There is no data for deleted rows, don't emit anything.
126+ }
127+ }
128+
129+ if write == WriteType :: Delete {
130+ // There is no data for deleted rows.
131+ return Ok ( ( ) ) ;
132+ }
133+
134+ write ! ( f, "json(powersync_diff(" ) ?;
135+
136+ if let Some ( ref old) = json_fragment_old {
137+ f. write_str ( old) ?;
138+ } else {
139+ // We don't have OLD values for inserts, we diff from an empty JSON object
140+ // instead.
141+ f. write_str ( "'{}'" ) ?;
142+ } ;
143+
144+ write ! ( f, ", {json_fragment_new}))" )
145+ } ) ) ,
146+ metadata : None :: < & ' static str > ,
147+ } ) ?;
45148 }
149+
150+ buffer. trigger_end ( ) ;
151+ Ok ( buffer. sql )
46152}
0 commit comments