@@ -210,15 +210,13 @@ lws_struct_sq3_deserialize(sqlite3 *pdb, const char *filter, const char *order,
210210 n + 1 == (int )schema -> child_map_size ? ' ' : ',' );
211211
212212 where [0 ] = '\0' ;
213- lws_snprintf ( where , sizeof ( where ), " where _lws_idx >= %llu %s" ,
214- ( unsigned long long ) start , filter ? filter : "" );
213+ if ( filter )
214+ lws_snprintf ( where , sizeof ( where ), " WHERE 1=1 %s" , filter );
215215
216216 lws_snprintf (s , sizeof (s ) - 1 , "select %s "
217- "from %s %s order by %s %slimit %d;" , results ,
217+ "from %s %s order by %s %slimit %d OFFSET %d ;" , results ,
218218 schema -> colname , where , order ,
219- _limit < 0 ? "desc " : "" , limit );
220-
221-
219+ _limit < 0 ? "desc " : "" , limit , start );
222220
223221 if (sqlite3_exec (pdb , s , lws_struct_sq3_deser_cb , & a , NULL ) != SQLITE_OK ) {
224222 lwsl_err ("%s: %s: fail %s\n" , __func__ , sqlite3_errmsg (pdb ), s );
@@ -431,6 +429,42 @@ _lws_struct_sq3_ser_one(sqlite3 *pdb, const lws_struct_map_t *schema,
431429 return 0 ;
432430}
433431
432+ /*
433+ * Serializes a single C member into its escaped SQL value representation.
434+ * Eg, a string "it's" becomes "'it''s'". An integer 123 becomes "123".
435+ */
436+ static void
437+ ls_sq3_serialize_col (const void * memb , const lws_struct_map_t * map ,
438+ char * * p , char * end )
439+ {
440+ char puri [1024 ];
441+ uint64_t uu64 ;
442+ size_t q ;
443+
444+ switch (map -> type ) {
445+ case LSMT_SIGNED :
446+ case LSMT_UNSIGNED :
447+ case LSMT_BOOLEAN :
448+ uu64 = 0 ;
449+ for (q = 0 ; q < map -> aux ; q ++ )
450+ uu64 |= ((uint64_t )((const uint8_t * )memb )[q ] << (q << 3 ));
451+
452+ if (map -> type == LSMT_SIGNED )
453+ * p += lws_snprintf (* p , lws_ptr_diff_size_t (end , * p ), "%lld" , (long long )(int64_t )uu64 );
454+ else
455+ * p += lws_snprintf (* p , lws_ptr_diff_size_t (end , * p ), "%llu" , (unsigned long long )uu64 );
456+ break ;
457+ case LSMT_STRING_CHAR_ARRAY :
458+ * p += lws_snprintf (* p , lws_ptr_diff_size_t (end , * p ), "'%s'" , lws_sql_purify (puri , memb , sizeof (puri )));
459+ break ;
460+ case LSMT_STRING_PTR :
461+ * p += lws_snprintf (* p , lws_ptr_diff_size_t (end , * p ), "'%s'" , lws_sql_purify (puri , * (const char * const * )memb , sizeof (puri )));
462+ break ;
463+ default :
464+ break ;
465+ }
466+ }
467+
434468int
435469lws_struct_sq3_serialize (sqlite3 * pdb , const lws_struct_map_t * schema ,
436470 lws_dll2_owner_t * owner , uint32_t manual_idx )
@@ -447,15 +481,150 @@ lws_struct_sq3_serialize(sqlite3 *pdb, const lws_struct_map_t *schema,
447481 return 0 ;
448482}
449483
484+ /*
485+ * UPDATE ... SET ... WHERE
486+ */
487+
488+ int
489+ lws_struct_sq3_update (sqlite3 * pdb , const char * table ,
490+ const lws_struct_map_t * map , size_t map_entries , const void * data ,
491+ const char * where_col )
492+ {
493+ char * q , * p , * end , subsequent = 0 ;
494+ size_t i ;
495+
496+ q = malloc (4096 );
497+ if (!q )
498+ return 1 ;
499+
500+ p = q ;
501+ end = q + 4096 ;
502+
503+ p += lws_snprintf (p , lws_ptr_diff_size_t (end , p ), "UPDATE %s SET " , table );
504+
505+ for (i = 0 ; i < map_entries ; i ++ ) {
506+ const void * memb = (const uint8_t * )data + map [i ].ofs ;
507+
508+ /* Don't try to UPDATE the primary key or the WHERE column itself */
509+ if ((i == 0 && map [i ].type == LSMT_UNSIGNED ) ||
510+ !strcmp (map [i ].colname , where_col ) ||
511+ map [i ].type == LSMT_BLOB_PTR )
512+ continue ;
513+
514+ if (subsequent )
515+ p += lws_snprintf (p , lws_ptr_diff_size_t (end , p ), "," );
516+ subsequent = 1 ;
517+
518+ p += lws_snprintf (p , lws_ptr_diff_size_t (end , p ), "%s=" , map [i ].colname );
519+ ls_sq3_serialize_col (memb , & map [i ], & p , end );
520+ }
521+
522+ p += lws_snprintf (p , lws_ptr_diff_size_t (end , p ), " WHERE %s=" , where_col );
523+
524+ for (i = 0 ; i < map_entries ; i ++ ) {
525+ if (!strcmp (map [i ].colname , where_col )) {
526+ const void * memb = (const uint8_t * )data + map [i ].ofs ;
527+ ls_sq3_serialize_col (memb , & map [i ], & p , end );
528+ break ;
529+ }
530+ }
531+
532+ if (sqlite3_exec (pdb , q , NULL , NULL , NULL ) != SQLITE_OK ) {
533+ lwsl_warn ("UPDATE failed: %s: %s\n" , q , sqlite3_errmsg (pdb ));
534+ free (q );
535+ return 1 ;
536+ }
537+
538+ free (q );
539+
540+ return sqlite3_changes (pdb ) == 0 ;
541+ }
542+
543+
544+ /*
545+ * INSERT ... ON CONFLICT DO UPDATE
546+ */
547+
548+ int
549+ lws_struct_sq3_upsert (sqlite3 * pdb , const char * table ,
550+ const lws_struct_map_t * map , size_t map_entries , const void * data ,
551+ const char * where_col )
552+ {
553+ char * q , * p , * end , subsequent = 0 ;
554+ size_t i ;
555+
556+ q = malloc (8192 );
557+ if (!q )
558+ return 1 ;
559+
560+ p = q ;
561+ end = q + 8192 ;
562+
563+ p += lws_snprintf (p , lws_ptr_diff_size_t (end , p ), "INSERT INTO %s (" , table );
564+
565+ for (i = 0 ; i < map_entries ; i ++ ) {
566+ /* Skip the primary key in the column list for INSERT */
567+ if ((i == 0 && map [i ].type == LSMT_UNSIGNED ) ||
568+ map [i ].type == LSMT_BLOB_PTR )
569+ continue ;
570+ if (subsequent )
571+ p += lws_snprintf (p , lws_ptr_diff_size_t (end , p ), "," );
572+ subsequent = 1 ;
573+ p += lws_snprintf (p , lws_ptr_diff_size_t (end , p ), "%s" , map [i ].colname );
574+ }
575+
576+ p += lws_snprintf (p , lws_ptr_diff_size_t (end , p ), ") VALUES (" );
577+ subsequent = 0 ;
578+
579+ /* Second pass for values, skipping the primary key */
580+ for (i = 0 ; i < map_entries ; i ++ ) {
581+ /* Skip the primary key in the value list for INSERT */
582+ if ((i == 0 && map [i ].type == LSMT_UNSIGNED ) ||
583+ map [i ].type == LSMT_BLOB_PTR )
584+ continue ;
585+ if (subsequent )
586+ p += lws_snprintf (p , lws_ptr_diff_size_t (end , p ), "," );
587+ subsequent = 1 ;
588+ ls_sq3_serialize_col ((const uint8_t * )data + map [i ].ofs , & map [i ], & p , end );
589+ }
590+
591+ p += lws_snprintf (p , lws_ptr_diff_size_t (end , p ), ") ON CONFLICT(%s) DO UPDATE SET " , where_col );
592+ subsequent = 0 ;
593+
594+ /* Third pass for the UPDATE SET part, skipping the PK and the where_col */
595+ for (i = 0 ; i < map_entries ; i ++ ) {
596+ if ((i == 0 && map [i ].type == LSMT_UNSIGNED ) || /* Skip PK */
597+ !strcmp (map [i ].colname , where_col ))
598+ continue ;
599+ if (subsequent )
600+ p += lws_snprintf (p , lws_ptr_diff_size_t (end , p ), "," );
601+ subsequent = 1 ;
602+ p += lws_snprintf (p , lws_ptr_diff_size_t (end , p ), "%s=" , map [i ].colname );
603+ ls_sq3_serialize_col ((const uint8_t * )data + map [i ].ofs , & map [i ], & p , end );
604+ }
605+
606+ if (sqlite3_exec (pdb , q , NULL , NULL , NULL ) != SQLITE_OK ) {
607+ lwsl_warn ("UPSERT failed: %s: %s\n" , q , sqlite3_errmsg (pdb ));
608+ free (q );
609+ return 1 ;
610+ }
611+
612+ free (q );
613+
614+ return 0 ;
615+ }
616+
450617int
451618lws_struct_sq3_create_table (sqlite3 * pdb , const lws_struct_map_t * schema )
452619{
453620 const lws_struct_map_t * map = schema -> child_map ;
454621 int map_size = (int )(ssize_t )schema -> child_map_size , subsequent = 0 ;
455622 char s [2048 ], * p = s , * end = & s [sizeof (s ) - 1 ],
456623 * pri = " primary key autoincrement" , * use ;
624+ int pk_done = 0 ;
457625
458626 p += lws_snprintf (p , (unsigned int )lws_ptr_diff (end , p ),
627+ /* The _lws_idx is for ordering, not a real key */
459628 "create table if not exists %s (_lws_idx integer, " ,
460629 schema -> colname );
461630
@@ -475,18 +644,18 @@ lws_struct_sq3_create_table(sqlite3 *pdb, const lws_struct_map_t *schema)
475644
476645 } else {
477646 if (map -> type < LSMT_STRING_CHAR_ARRAY ) {
647+ /* This is an integer-type column */
478648 use = "" ;
479- if (map -> colname [ 0 ] != '_' ) /* _lws_idx is not primary key */
649+ if (! pk_done ) {
480650 use = pri ;
651+ pk_done = 1 ;
652+ }
481653 p += lws_snprintf (p , (unsigned int )lws_ptr_diff (end , p ), "%s integer%s" ,
482654 map -> colname , use );
483- if (map -> colname [0 ] != '_' )
484- pri = "" ;
485655 } else
486656 p += lws_snprintf (p , (unsigned int )lws_ptr_diff (end , p ), "%s varchar" ,
487657 map -> colname );
488658 }
489-
490659 map ++ ;
491660 }
492661
@@ -498,6 +667,13 @@ lws_struct_sq3_create_table(sqlite3 *pdb, const lws_struct_map_t *schema)
498667 return -1 ;
499668 }
500669
670+ /* Ensure the UNIQUE constraint for upserts exists */
671+ p = s ;
672+ p += lws_snprintf (p , (unsigned int )lws_ptr_diff (end , p ),
673+ "CREATE UNIQUE INDEX IF NOT EXISTS name_idx ON %s (name)" ,
674+ schema -> colname );
675+ sqlite3_exec (pdb , s , NULL , NULL , NULL );
676+
501677 return 0 ;
502678}
503679
0 commit comments