fn insert_bookmark_in_tx()

in components/places/src/storage/bookmarks.rs [287:413]


fn insert_bookmark_in_tx(db: &PlacesDb, bm: InsertableItem) -> Result<SyncGuid> {
    // find the row ID of the parent.
    if bm.parent_guid() == BookmarkRootGuid::Root {
        return Err(InvalidPlaceInfo::CannotUpdateRoot(BookmarkRootGuid::Root).into());
    }
    let parent_guid = bm.parent_guid();
    let parent = get_raw_bookmark(db, parent_guid)?
        .ok_or_else(|| InvalidPlaceInfo::NoSuchGuid(parent_guid.to_string()))?;
    if parent.bookmark_type != BookmarkType::Folder {
        return Err(InvalidPlaceInfo::InvalidParent(parent_guid.to_string()).into());
    }
    // Do the "position" dance.
    let position = resolve_pos_for_insert(db, *bm.position(), &parent)?;

    // Note that we could probably do this 'fk' work as a sub-query (although
    // markh isn't clear how we could perform the insert) - it probably doesn't
    // matter in practice though...
    let fk = match bm {
        InsertableItem::Bookmark { ref b } => {
            let page_info = match fetch_page_info(db, &b.url)? {
                Some(info) => info.page,
                None => new_page_info(db, &b.url, None)?,
            };
            Some(page_info.row_id)
        }
        _ => None,
    };
    let sql = "INSERT INTO moz_bookmarks
              (fk, type, parent, position, title, dateAdded, lastModified,
               guid, syncStatus, syncChangeCounter) VALUES
              (:fk, :type, :parent, :position, :title, :dateAdded, :lastModified,
               :guid, :syncStatus, :syncChangeCounter)";

    let guid = bm.guid().clone().unwrap_or_else(SyncGuid::random);
    if !guid.is_valid_for_places() || !guid.is_valid_for_sync_server() {
        return Err(InvalidPlaceInfo::InvalidGuid.into());
    }
    let date_added = bm.date_added().unwrap_or_else(Timestamp::now);
    // last_modified can't be before date_added
    let last_modified = max(
        bm.last_modified().unwrap_or_else(Timestamp::now),
        date_added,
    );

    let bookmark_type = bm.bookmark_type();
    match bm {
        InsertableItem::Bookmark { ref b } => {
            let title = maybe_truncate_title(&b.title.as_deref());
            db.execute_cached(
                sql,
                &[
                    (":fk", &fk as &dyn rusqlite::ToSql),
                    (":type", &bookmark_type),
                    (":parent", &parent.row_id),
                    (":position", &position),
                    (":title", &title),
                    (":dateAdded", &date_added),
                    (":lastModified", &last_modified),
                    (":guid", &guid),
                    (":syncStatus", &SyncStatus::New),
                    (":syncChangeCounter", &1),
                ],
            )?;
        }
        InsertableItem::Separator { .. } => {
            db.execute_cached(
                sql,
                &[
                    (":type", &bookmark_type as &dyn rusqlite::ToSql),
                    (":parent", &parent.row_id),
                    (":position", &position),
                    (":dateAdded", &date_added),
                    (":lastModified", &last_modified),
                    (":guid", &guid),
                    (":syncStatus", &SyncStatus::New),
                    (":syncChangeCounter", &1),
                ],
            )?;
        }
        InsertableItem::Folder { f } => {
            let title = maybe_truncate_title(&f.title.as_deref());
            db.execute_cached(
                sql,
                &[
                    (":type", &bookmark_type as &dyn rusqlite::ToSql),
                    (":parent", &parent.row_id),
                    (":title", &title),
                    (":position", &position),
                    (":dateAdded", &date_added),
                    (":lastModified", &last_modified),
                    (":guid", &guid),
                    (":syncStatus", &SyncStatus::New),
                    (":syncChangeCounter", &1),
                ],
            )?;
            // now recurse for children
            for mut child in f.children.into_iter() {
                // As a special case for trees, each child in a folder can specify
                // Guid::Empty as the parent.
                let specified_parent_guid = child.parent_guid();
                if specified_parent_guid.is_empty() {
                    child.set_parent_guid(guid.clone());
                } else if *specified_parent_guid != guid {
                    return Err(
                        InvalidPlaceInfo::InvalidParent(specified_parent_guid.to_string()).into(),
                    );
                }
                // If children have defaults for last_modified and date_added we use the parent
                if child.last_modified().is_none() {
                    child.set_last_modified(last_modified);
                }
                if child.date_added().is_none() {
                    child.set_date_added(date_added);
                }
                insert_bookmark_in_tx(db, child)?;
            }
        }
    };

    // Bump the parent's change counter.
    let sql_counter = "
        UPDATE moz_bookmarks SET syncChangeCounter = syncChangeCounter + 1
        WHERE id = :parent_id";
    db.execute_cached(sql_counter, &[(":parent_id", &parent.row_id)])?;

    Ok(guid)
}