> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ditto.live/llms.txt
> Use this file to discover all available pages before exploring further.

# Release Notes

> Explore release notes across every Ditto product.

export function ReleaseNotesPager() {
  const currentPage = () => {
    const m = window.location.pathname.match(/\/page-(\d+)$/);
    return m ? Number(m[1]) : 1;
  };
  const base = () => window.location.pathname.replace(/\/page-\d+$/, "");
  const target = pageNum => (pageNum === 1 ? base() : base() + "/page-" + pageNum) + window.location.search;
  const [total, setTotal] = useState(null);
  useEffect(() => {
    const el = document.querySelector("[data-release-notes-filters]");
    const n = el ? Number(el.getAttribute("data-rn-total-pages")) : NaN;
    if (Number.isFinite(n) && n >= 1) setTotal(n);
  }, []);
  if (!total || total <= 1) return null;
  const cur = currentPage();
  const hasNewer = cur > 1;
  const hasOlder = cur < total;
  const propsFor = (pageNum, rel) => ({
    rel,
    href: target(pageNum),
    onMouseEnter: e => {
      e.currentTarget.href = target(pageNum);
    },
    onMouseDown: e => {
      e.currentTarget.href = target(pageNum);
    },
    onFocus: e => {
      e.currentTarget.href = target(pageNum);
    },
    onClick: e => {
      if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
      e.preventDefault();
      window.location.assign(target(pageNum));
    }
  });
  return <nav id="release-notes-pager" className="rnp rn-upd" aria-label="Release notes pages">
      <style>{`
        /* Bottom-of-stream pagination: belongs to the main content column
           (.rn-upd width), visually separated from the final announcement by
           spacing + a subtle top boundary (Mintlify-neutral conventions).
           The id + scroll-margin are the anchor contract for the TOC's
           "More releases ↓" jump link (Mintlify's own --scroll-mt keeps the
           landed position below the navbar); both are additive — the pager's
           appearance and behavior are otherwise unchanged. */
        .rnp { box-sizing: border-box; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-top: 48px; padding: 20px 0 8px; border-top: 1px solid rgba(23, 23, 23, 0.1); font-size: 14px; scroll-margin-top: var(--scroll-mt, 96px); }
        html.dark .rnp { border-top-color: rgba(255, 255, 255, 0.12); }
        .rnp-pos { font-size: 12px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: #6b7280; }
        html.dark .rnp-pos { color: #9ca3af; }
        .rnp-link { display: inline-flex; align-items: center; min-height: 36px; padding: 0 14px; border-radius: 9999px; border: 1px solid rgba(23, 23, 23, 0.15); color: #374151; font-weight: 600; text-decoration: none; }
        .rnp-link:hover { border-color: #111827; color: #111827; }
        html.dark .rnp-link { border-color: rgba(255, 255, 255, 0.16); color: #d1d5db; }
        html.dark .rnp-link:hover { border-color: #e5e7eb; color: #e5e7eb; }
        .rnp-link:focus-visible { outline: 2px solid #688ae8; outline-offset: 2px; }
        @media (max-width: 560px) {
          .rnp { flex-direction: column; align-items: flex-start; }
        }
      `}</style>
      {hasNewer ? <a className="rnp-link" {...propsFor(cur - 1, "prev")}>← Newer releases</a> : <span className="rnp-pos">Page {cur} of {total}</span>}
      {hasNewer && hasOlder ? <span className="rnp-pos">Page {cur} of {total}</span> : null}
      {hasOlder ? <a className="rnp-link" {...propsFor(cur + 1, "next")}>Older releases →</a> : hasNewer ? <span className="rnp-pos">Page {cur} of {total}</span> : null}
    </nav>;
}

export function ReleaseNotesFilters({view, page, totalPages, tocEntries, rangeStart, rangeEnd, rangeTotal}) {
  const [open, setOpen] = useState(false);
  const controlRef = useRef(null);
  const basePath = window.location.pathname.replace(/\/page-\d+$/, "");
  const WRAPPER_PREFIX = "/sdk/latest/release-notes-hub/";
  const activeId = basePath === "/release-notes" ? "all" : basePath === "/release-notes/sdk" ? "sdk-all" : basePath === "/release-notes/common" ? "common" : basePath === "/release-notes/cloud" ? "cloud" : basePath.startsWith("/release-notes/sdk/") ? basePath.slice(("/release-notes/sdk/").length) : basePath.startsWith(WRAPPER_PREFIX) ? basePath.slice(WRAPPER_PREFIX.length) : basePath === "/cloud/release-notes-hub" ? "cloud" : "all";
  const tocSlug = label => label.toLowerCase().replace(/[(),]/g, "").replace(/[.\s]/g, "-");
  const TOC_GROUP_ORDER = ["SDK", "Cloud Platform"];
  const grouped = view === "all" || view === "sdk-all" ? TOC_GROUP_ORDER.map(g => ({
    group: g,
    entries: tocEntries.filter(e => e.group === g)
  })).filter(g => g.entries.length > 0) : [{
    group: null,
    entries: tocEntries
  }];
  const tocRow = entry => <li key={entry.label}>
      <a className="rnff-toc-link" href={"#" + tocSlug(entry.label)}>
        {entry.label}
      </a>
    </li>;
  const VIEW_HREF = {
    "all": "/release-notes",
    "sdk-all": "/release-notes/sdk",
    "common": "/release-notes/common",
    "cloud": "/release-notes/cloud"
  };
  const viewHref = id => VIEW_HREF[id] || `/release-notes/sdk/${id}`;
  const PLATFORM_LINKS = [["swift", "Swift"], ["kotlin", "Kotlin"], ["flutter", "Flutter"], ["react-native", "React Native"], ["javascript-web", "JavaScript (Web)"], ["javascript-nodejs", "Node.js"], ["java", "Java"], ["c-sharp", "C#"], ["cpp", "C++"], ["rust", "Rust"], ["go", "Go"]];
  const DEST_ICONS = {
    "all": ["clipboard-list", "solid"],
    "sdk-all": ["laptop-mobile", "solid"],
    "common": ["cubes-stacked", "solid"],
    "cloud": ["cloud", "regular"]
  };
  const PLATFORM_ICONS = {
    "swift": ["apple", "brands"],
    "kotlin": ["android", "brands"],
    "flutter": ["flutter", "brands"],
    "react-native": ["react", "brands"],
    "javascript-web": ["js", "brands"],
    "javascript-nodejs": ["js", "brands"],
    "java": ["java", "brands"],
    "c-sharp": ["microsoft", "brands"],
    "cpp": ["code", "solid"],
    "rust": ["rust", "brands"],
    "go": ["golang", "brands"]
  };
  useEffect(() => {
    if (!open) return;
    const onDown = e => {
      if (controlRef.current && !controlRef.current.contains(e.target)) setOpen(false);
    };
    const onFocus = e => {
      if (controlRef.current && !controlRef.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener("mousedown", onDown);
    document.addEventListener("focusin", onFocus);
    return () => {
      document.removeEventListener("mousedown", onDown);
      document.removeEventListener("focusin", onFocus);
    };
  }, [open]);
  const destLink = (id, label, icon) => <a className="rnff-dest" data-active={activeId === id ? "true" : undefined} href={viewHref(id)}>
      {icon && <span className="rnff-dest-icon">
          <Icon icon={icon[0]} iconType={icon[1]} />
        </span>}
      <span>{label}</span>
    </a>;
  return <div className="rnff-stick not-prose" data-release-notes-filters="" data-rn-total-pages={totalPages}>
      {}
      <style>{`
        /* RESPONSIVE BREAKOUT SYSTEM: card 290px, gap 36px; the rail's sticky
           top tracks Mintlify's NATIVE right-rail offset (--scroll-mt =
           navbar bottom + 40, banner-aware, maintained live by Mintlify's own
           scroll-margin script; 180px fallback until that script runs).
           Below 1260px: normal single-column flow. */
        :root {
          --rn-card-w: 290px;
          --rn-gap: 36px;
          --rn-off: min(max(calc(100vw - 1204px), 0px), 350px);
        }
        .rn-upd { width: min(calc(100% - var(--rn-card-w) - var(--rn-gap) + var(--rn-off)), 100%); }
        /* COPY PAGE = STANDARD-LAYOUT POSITION (stakeholder polish): on
           rail-active desktop viewports, Mintlify's page-context menu (Copy
           page) shifts left so its right edge aligns with the releases
           column's right edge — the same relationship standard docs pages
           have (button over the article column, right rail beside it). The
           margin mirrors the .rn-upd width formula (card + gap - off), so it
           stays correct at every width with no tuning of its own; max()
           clamps it to 0 where the releases column is full-width (~1530px+,
           the same point .rn-upd's min() caps). Gated to the rail-active
           conditions (min-width 1261, min-height 641) so the collapsed
           layouts below keep the native header placement. This stylesheet
           only loads on release-notes routes, so no other page's Copy Page
           button can be affected. */
        @media (min-width: 1261px) and (min-height: 641px) {
          #page-context-menu { margin-right: max(0px, calc(var(--rn-card-w) + var(--rn-gap) - var(--rn-off))); }
        }
        /* STICKY BOUNDARY (corrected): the wrapper's box height = the rail's
           real visual height formula, compensated by an equal negative top
           margin; the sticky containment rule then measures the rail's TRUE
           extent, so the rail stops at the end of the release-notes content
           region instead of floating into Mintlify's native prev/next +
           footer area. */
        .rnff-stick { box-sizing: border-box; overflow: visible; width: var(--rn-card-w); max-width: none; margin-left: auto; position: sticky; top: var(--scroll-mt, 180px); height: max(240px, calc(100vh - var(--scroll-mt, 180px) - 24px)); margin-top: min(-240px, calc(var(--scroll-mt, 180px) + 24px - 100vh)); margin-bottom: 0; transform: translateX(var(--rn-off)); }
        .rnff-card { border: 1px solid rgba(23, 23, 23, 0.1); border-radius: 16px; padding: 16px 20px; background: #ffffff; }
        html.dark .rnff-card { border-color: rgba(255, 255, 255, 0.12); background: #171717; }
        .rnff-label { font-family: Inter, -apple-system, system-ui, "Segoe UI", sans-serif; font-size: 14px; font-weight: 500; line-height: 24px; letter-spacing: normal; text-transform: none; color: #171717; margin: 0 0 4px; }
        html.dark .rnff-label { color: #dfdfdf; }
        .rnff-group + .rnff-group { margin-top: 16px; }
        /* Destination rows — typography measured from the existing LEFT
           sidebar (2026-09-16): nav item = 14px / 400 / 24px / normal case /
           #3f3f3f (dark #9f9f9f), font stack Inter/system; CURRENT item =
           same weight, darker color #1e1e1e. Section labels match the
           "Key Concepts" group header (14px / 500 / normal case). Rows keep
           the sidebar shape: full-width rectangular rows + icon gutter, NOT
           chips/pills. */
        .rnff-dest { display: flex; align-items: center; gap: 8px; padding: 3px 8px; margin: 0 -8px; font-family: Inter, -apple-system, system-ui, "Segoe UI", sans-serif; font-size: 14px; font-weight: 400; line-height: 24px; color: #3f3f3f; text-decoration: none; border-radius: 8px; }
        .rnff-dest:hover { color: #111827; background: rgba(23, 23, 23, 0.04); }
        html.dark .rnff-dest { color: #9f9f9f; }
        html.dark .rnff-dest:hover { color: #e5e7eb; background: rgba(255, 255, 255, 0.06); }
        .rnff-dest[data-active="true"] { font-weight: 400; color: #1e1e1e; background: rgba(23, 23, 23, 0.06); }
        html.dark .rnff-dest[data-active="true"] { color: #e5e5e5; background: rgba(255, 255, 255, 0.1); }
        .rnff-dest:focus-visible { outline: 2px solid #688ae8; outline-offset: 2px; }
        .rnff-dest-icon { display: inline-flex; width: 16px; flex: 0 0 16px; justify-content: center; color: inherit; opacity: 0.85; }
        .rnff-dest-icon svg { width: 14px; height: 14px; }
        /* Platform disclosure control — a field-shaped button that reveals
           the destination link list IN FLOW (no overlay, no overflow risk,
           no z-index games). Closed height matches the replaced select (~34px)
           so the vertical-space win from the dropdown change is retained. */
        .rnff-plat-toggle { display: flex; align-items: center; gap: 8px; width: 100%; height: 34px; padding: 0 10px; font-family: Inter, -apple-system, system-ui, "Segoe UI", sans-serif; font-size: 14px; font-weight: 400; line-height: 24px; text-align: left; color: #3f3f3f; background: #ffffff; border: 1px solid rgba(23, 23, 23, 0.15); border-radius: 8px; cursor: pointer; }
        html.dark .rnff-plat-toggle { background: #171717; color: #9f9f9f; border-color: rgba(255, 255, 255, 0.16); }
        .rnff-plat-toggle:hover { border-color: #111827; }
        html.dark .rnff-plat-toggle:hover { border-color: #e5e7eb; }
        .rnff-plat-toggle:focus-visible { outline: 2px solid #688ae8; outline-offset: 2px; }
        .rnff-plat-label { flex: 1 1 auto; }
        .rnff-plat-caret { display: inline-flex; color: #9ca3af; }
        .rnff-plat-caret svg { width: 12px; height: 12px; }
        /* OPEN MENU OVERLAYS the content below (incl. the TOC) like a normal
           dropdown/popover: absolute within .rnff-plat (position:relative),
           so opening never grows the card or pushes the TOC down. All 11
           options render at once — no max-height, no internal scroll.
           Clipping: no ancestor hides overflow (stick/rail/card are all
           overflow:visible), so absolute positioning is safe. */
        .rnff-plat { position: relative; }
        .rnff-plat-list { list-style: none; position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 50; margin: 0; padding: 4px 0; background: #ffffff; border: 1px solid rgba(23, 23, 23, 0.12); border-radius: 8px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); }
        html.dark .rnff-plat-list { background: #171717; border-color: rgba(255, 255, 255, 0.14); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); }
        /* Dropdown option spacing (manual-review polish): rows inside the
           menu get a roomier, consistent inset — icons sit 12px from the
           menu edge with a 12px icon→label gap (the site's 4px spacing
           scale), so all 11 icons share one column and all labels share one
           text column. Row heights and menu dimensions are unchanged. */
        .rnff-plat-list .rnff-dest { padding: 3px 12px; gap: 12px; margin: 0; }
        /* Uppercase platform labels inside the dropdown only (stakeholder
           presentation request — matches the site's Aeonik/uppercase brand
           usage for navigation accents). Route ids/labels/metadata are
           unchanged; this is a visual transform of the option text. */
        .rnff-plat-label, .rnff-plat-list .rnff-dest span:last-child { text-transform: uppercase; }
        @media (max-width: 1260px) {
          .rn-upd { width: 100%; }
          .rnff-stick { transform: none; width: 100%; height: auto; position: static; margin: 0 0 24px; }
        }
        /* Extremely SHORT viewports: same static collapse as narrow screens. */
        @media (max-height: 640px) and (min-width: 1261px) {
          .rn-upd { width: 100%; }
          .rnff-stick { transform: none; width: 100%; height: auto; position: static; margin: 0 0 24px; }
          .rnff-rail { max-height: none; }
        }
        /* RIGHT-RAIL AS ONE BOUNDED REGION (unchanged): flex column capped at
           the viewport height remaining under the sticky top offset; the
           destination card never shrinks; the TOC takes leftover space and
           scrolls independently. */
        .rnff-rail { display: flex; flex-direction: column; max-height: max(240px, calc(100vh - var(--scroll-mt, 180px) - 24px)); }
        .rnff-card { flex: 0 0 auto; }
        .rnff-toc { flex: 1 1 auto; min-height: 0; margin-top: 20px; padding: 10px 12px; border: 1px solid rgba(23, 23, 23, 0.08); border-radius: 12px; overflow-y: auto; overscroll-behavior: contain; }
        html.dark .rnff-toc { border-color: rgba(255, 255, 255, 0.1); }
        /* TOC: entries match the site's NATIVE right-rail TOC convention
           (styles.css table-of-contents rule: Aeonik Fono + uppercase;
           measured native entries: 14px / 400 / 24px, header 14px / 500).
           NOTE: no backticks inside this <style> template literal — a
           backtick terminates the template and silently kills the whole
           component's export (measured the hard way). */
        .rnff-toc-label { font-family: Inter, -apple-system, system-ui, "Segoe UI", sans-serif; font-size: 14px; font-weight: 500; line-height: 24px; letter-spacing: normal; text-transform: none; color: #171717; margin: 0 0 4px; }
        html.dark .rnff-toc-label { color: #dfdfdf; }
        /* TOC header row: label left, announcement range right (same row,
           visually subordinate — 12px grey, the group-label's muted tones).
           Range values arrive as generated route props (registry-derived);
           the component never computes them. */
        .rnff-toc-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
        .rnff-toc-range { font-family: 'Aeonik Fono', monospace; font-size: 12px; font-weight: 400; line-height: 24px; letter-spacing: normal; text-transform: none; color: #9ca3af; }
        html.dark .rnff-toc-range { color: #6b7280; }
        /* "More releases ↓" — TOC-footer jump link to the existing bottom
           pager (#release-notes-pager). Additive navigation only: rendered
           solely when the generated route says the view is paginated
           (totalPages > 1), so it never points at an unmounted pager. */
        .rnff-toc-more { margin-top: 8px; padding-top: 8px; border-top: 1px solid rgba(23, 23, 23, 0.08); }
        html.dark .rnff-toc-more { border-top-color: rgba(255, 255, 255, 0.1); }
        .rnff-toc-group-label { font-family: 'Aeonik Fono', monospace; font-size: 12px; font-weight: 500; letter-spacing: normal; text-transform: uppercase; color: #9ca3af; margin: 12px 0 4px; }
        html.dark .rnff-toc-group-label { color: #6b7280; }
        .rnff-toc ul { list-style: none; margin: 0; padding: 0; }
        .rnff-toc li { margin: 0; }
        .rnff-toc-link { display: block; padding: 3px 0; font-family: 'Aeonik Fono', monospace; font-size: 14px; font-weight: 400; line-height: 24px; letter-spacing: normal; text-transform: uppercase; color: #707070; text-decoration: none; }
        .rnff-toc-link:hover { color: #1e1e1e; text-decoration: underline; }
        html.dark .rnff-toc-link { color: #9f9f9f; }
        html.dark .rnff-toc-link:hover { color: #e5e5e5; }
        .rnff-toc a:focus-visible { outline: 2px solid #688ae8; outline-offset: 2px; border-radius: 4px; }
        @media (max-width: 1260px) {
          .rnff-toc { display: none; }
        }
      `}</style>
      <div className="rnff-rail">
      <div className="rnff-card">
      <div role="group" aria-label="Release Notes" className="rnff-group">
        <p className="rnff-label">Release Notes</p>
        <nav aria-label="Release notes destinations">
          {destLink("all", "All", DEST_ICONS["all"])}
          {destLink("sdk-all", "SDK - All", DEST_ICONS["sdk-all"])}
          {destLink("common", "SDK - Common", DEST_ICONS["common"])}
          {destLink("cloud", "Cloud Platform", DEST_ICONS["cloud"])}
        </nav>
      </div>

      <div role="group" aria-label="SDK Platforms" className="rnff-group">
        <p className="rnff-label">SDK Platforms</p>
        {}
        {(() => {
    const activeIsPlatform = !!PLATFORM_ICONS[activeId];
    const activeLabel = activeIsPlatform ? PLATFORM_LINKS.find(([s]) => s === activeId)?.[1] : null;
    return <div className="rnff-plat" ref={controlRef}>
              <button type="button" className="rnff-plat-toggle" aria-expanded={open ? "true" : "false"} aria-controls="rn-platform-destinations" onClick={() => setOpen(!open)} onKeyDown={e => {
      if (e.key === "ArrowDown" && !open) {
        e.preventDefault();
        setOpen(true);
      }
      if (e.key === "Escape" && open) {
        e.preventDefault();
        setOpen(false);
      }
    }}>
                {activeIsPlatform && <span className="rnff-dest-icon">
                    <Icon icon={PLATFORM_ICONS[activeId][0]} iconType={PLATFORM_ICONS[activeId][1]} />
                  </span>}
                <span className="rnff-plat-label">
                  {activeLabel || "Select a platform…"}
                </span>
                <span className="rnff-plat-caret" aria-hidden="true">
                  <Icon icon={open ? "chevron-up" : "chevron-down"} iconType="solid" />
                </span>
              </button>
              {open && <ul id="rn-platform-destinations" className="rnff-plat-list" aria-label="SDK platform destinations">
                  {PLATFORM_LINKS.map(([slug, label]) => <li key={slug}>
                      <a className="rnff-dest" data-active={activeId === slug ? "true" : undefined} href={viewHref(slug)} onKeyDown={e => {
      const links = [...controlRef.current.querySelectorAll(".rnff-plat-list a")];
      const i = links.indexOf(e.currentTarget);
      if (e.key === "ArrowDown") {
        e.preventDefault();
        (links[i + 1] || links[0]).focus();
      }
      if (e.key === "ArrowUp") {
        e.preventDefault();
        (links[i - 1] || links[links.length - 1]).focus();
      }
      if (e.key === "Escape") {
        e.preventDefault();
        setOpen(false);
        controlRef.current.querySelector(".rnff-plat-toggle").focus();
      }
    }}>
                        <span className="rnff-dest-icon">
                          <Icon icon={PLATFORM_ICONS[slug][0]} iconType={PLATFORM_ICONS[slug][1]} />
                        </span>
                        <span>{label}</span>
                      </a>
                    </li>)}
                </ul>}
            </div>;
  })()}
      </div>
      </div>
      <nav className="rnff-toc" aria-label="Release notes table of contents">
        <div className="rnff-toc-head">
          <p className="rnff-toc-label">Table of Contents</p>
          {rangeTotal > 0 && <span className="rnff-toc-range">{rangeStart}–{rangeEnd} of {rangeTotal}</span>}
        </div>
        {grouped.map(g => <div key={g.group || "flat"}>
            {g.group && <p className="rnff-toc-group-label">{g.group}</p>}
            <ul>
              {g.entries.map(entry => tocRow(entry))}
            </ul>
          </div>)}
        {totalPages > 1 && <a className="rnff-toc-link rnff-toc-more" href="#release-notes-pager">More releases ↓</a>}
      </nav>
    </div>
    </div>;
}

<ReleaseNotesFilters view="rust" page={1} totalPages={1} rangeStart={1} rangeEnd={7} rangeTotal={7} tocEntries={[{"label":"5.1.0","group":"SDK"},{"label":"5.1.0 (Rust)","group":"SDK"},{"label":"5.0.3","group":"SDK"},{"label":"5.0.2","group":"SDK"},{"label":"5.0.1","group":"SDK"},{"label":"5.0.0","group":"SDK"},{"label":"5.0.0 (Rust)","group":"SDK"}]} />

<Update label="5.1.0" description="Release Date: Aug 18, 2026" tags={["SDK", "SDK-Common"]} className="rn-upd">
  \_*SDK 5.1 common capabilities — shared by all SDK platforms. Shown in the overall SDK view; platform filters show that platform's own blocks only.*

  # Ditto SDK 5.1 — Faster Local Queries. More Capable Query Engine.

  The new Ditto Edge SDK 5.1 delivers major features and optimizations across five areas. The release includes 77 platform and 112 SDK-specific improvements.

  **Key takeaway**: Existing applications get better performance and more efficient memory usage on the same hardware simply by upgrading to SDK 5.1.

  **[1. Performance: Faster Queries, Lower Memory Use](#1-performance-faster-queries-lower-memory-use)**

  * Evictions are 53.6× faster and deletes are 43.0× faster on average. This speeds up routine local data clean-up, like clearing the orders from a point-of-sale terminal.
  * A full-collection `COUNT(*)` fell from 149.03 ms to 0.89 ms, which is 167× faster on average. This speeds up badge counts and dashboard totals across an app.

  **[2. Query Engine: New Ditto Query Capabilities](#2-query-engine-new-ditto-query-capabilities)**

  * `JOIN` combines local collections in a single `SELECT`, removing the need to run separate queries and merge their results in application code.
  * `ADVISE` looks at a query and tells you the indexes to create to make it faster, all without running it or reading a document.

  **[3. Security: Expanded Certificate Revocation](#3-security-expanded-certificate-revocation)**

  * If a device is lost or stolen, it's easier to revoke the device's certificate, disconnect it from the mesh, and prevent the device from sending or receiving data.
  * Revocations travel peer to peer and reach devices even when they can't see the cloud. Connections from revoked peers are terminated and refused.

  **[4. Troubleshooting: Identify Problems and Recover Automatically](#4-troubleshooting-identify-problems-and-recover-automatically)**

  * Identifying problems is easier with better tools for analyzing deployed devices, starting with support bundles that capture the device's effective configuration.
  * Corrupted replication metadata is detected and rebuilt automatically, so field issues can be solved without a site visit.

  **[5. Transport at Scale: Multicast Beta](#5-transport-at-scale-multicast-beta)**

  * Multicast allows Ditto to scale to large networks with many edge devices. Edge devices subscribe to a shared group for data sync, which has lower connection cost at scale.
  * Opt-in beta capability in the Swift, Kotlin/KMP, Flutter, and Rust SDKs. Support will expand to additional SDKs in the future.

  **[Upgrading to 5.1 and rollback](#upgrading-to-5-1)**

  * Upgrading to Ditto SDK 5.1 changes the on-disk index format.
  * If you need to downgrade from Ditto SDK 5.1 or later, migrate through Ditto 5.0.2+. You can also migrate through Ditto 4.14.6+.

  # 1. Performance: Faster Queries, Lower Memory Use

  Edge applications feel database performance differently from cloud applications. A slow local query doesn't just delay a response from a server. It can block a screen transition, increase battery use, create UI churn, or consume memory on a device with limited resources.

  SDK 5.1 delivers a major leap in local query performance. Applications can load data faster, complete writes sooner, and react to changes more quickly—even as their local datasets and workloads grow.

  The improvements span nearly every kind of local data operation: selects, indexed reads, inserts, updates, deletes, evictions, aggregations, and observers. In practice, this means more responsive user experiences, less time waiting for data operations, and greater capacity on the same device hardware.

  These gains come from improvements throughout the local data path. Ditto performs less decoding and allocation, finds documents more directly, executes mutations more efficiently, avoids unnecessary observer work, and reduces full database scans during subscription changes. An opt-in relaxed durability mode can further reduce disk synchronization for rebuildable sync metadata without changing the durability of application documents.

  ## Android retail benchmark

  The gains are broad rather than limited to one optimized query path. In testing on an Orion O6 Android device, SDK 5.1 was faster in 70 of 71 measured scenarios, with one result too close to call. The 72-scenario suite modeled an offline-first retail application with approximately 93,000 documents across seven synced collections.

  | DQL operation  | Purpose                                      | Geometric-mean speedup |
  | -------------- | -------------------------------------------- | ---------------------: |
  | Evict          | Clear local data without syncing the removal |                  53.6× |
  | Delete         | Remove documents everywhere                  |                  43.0× |
  | Update         | Change fields in existing documents          |                  18.5× |
  | Aggregation    | Compute totals and averages                  |                   4.2× |
  | Select         | Read documents that match a query            |                   1.5× |
  | Indexed select | Read matches using an index                  |                   1.4× |
  | Insert         | Create new documents                         |                   1.2× |

  These results compare median runtimes from Ditto 5.0.3 and Ditto 5.1.0. Performance varies by device, data, indexes, and query mix, so test representative workloads on your target hardware.

  ### Dramatically faster document counts

  A count is a query that answers one simple question: how many documents match? Apps use counts all the time to show how many orders are open, how many items are in stock, or how many results a search found.

  One of the largest individual improvements is full-collection document counting. Ditto 5.1.0 adds an optimized path for `COUNT(*)`, reducing the benchmark's median execution time from 149.03 ms to 0.89 ms—approximately **167× faster**. Counts with a filter also improved by approximately **4.4×**.

  | Query                  | Ditto 5.0.3 | Ditto 5.1.0 | Speedup |
  | ---------------------- | ----------: | ----------: | ------: |
  | Full-collection count  |   149.03 ms |     0.89 ms |    167× |
  | Count with a condition |    60.85 ms |    13.89 ms |    4.4× |

  These improvements accelerate queries such as `SELECT COUNT(*) FROM tasks` and `SELECT COUNT(*) FROM tasks WHERE status = 'open'`, making it substantially faster to calculate totals for dashboards, backlog checks, pagination, and application status displays.

  ## Lower memory use

  Edge devices like phones, tablets, and point-of-sale terminals have a fixed amount of memory that every app shares. The less memory Ditto uses, the more room your app has for its own work, and the less likely the operating system is to slow it down.

  In a separate Android workload that grew a collection from 3,000 to 30,000 documents, Ditto 5.1.0 delivered approximately 2.1× as many observer results while using less memory than Ditto 5.0.3.

  | Memory measurement | Ditto 5.0.3 | Ditto 5.1.0 | Improvement |
  | ------------------ | ----------: | ----------: | ----------: |
  | Median Total PSS   |    271.4 MB |    233.5 MB | 14.0% lower |
  | Median native heap |    180.5 MB |    121.4 MB | 32.7% lower |
  | Peak Total PSS     |    469.5 MB |    400.6 MB | 14.7% lower |

  Total PSS estimates the process's physical RAM footprint, and peak Total PSS is the highest that footprint reached during the test. Native heap covers Ditto's Rust core—the pool of memory the program sets aside while it runs to hold its working data. Tombstone cleanup, bulk mutations, and retained disconnected sync sessions are also bounded more carefully to reduce peak memory in high-volume deployments.

  ***

  # 2. Query Engine: New Ditto Query Capabilities

  Performance is only half of the query story in this new release. DQL (Ditto Query Language) gains the two capabilities customers asked for the most: `JOIN` and composite indexes. Customers can also run `ADVISE` to identify any indexes that would speed up query performance.

  ## Join collections locally

  A join is a query that combines related data from two collections into one result. It matches records that share a value, like a task and the project it belongs to, so your application gets one combined answer instead of two separate lists.

  `SELECT` statements on edge devices can now [join multiple local collections](/dql/select#joins). This removes the need to coordinate separate queries and merge their results in application code.

  ```sql DQL theme={null}
  SELECT task._id, task.title, project.name
  FROM tasks AS task
  JOIN projects AS project ON task.projectId = project._id
  WHERE task.status = 'open'
  ```

  This makes normalized data models practical at the edge. Product catalogs, order histories, task assignments, and multi-tenant views can stay separated into logical collections without forcing every screen to coordinate multiple reads.

  Joins use data already present in the local store. They do not fetch missing data from peers, and they are not supported in sync-subscription queries. The inner collection normally requires an appropriate index; use [`ADVISE`](/dql/advise) when you need an index recommendation.

  ## Create composite indexes

  An index is a lookup structure the database maintains so it can find matching documents without reading the whole collection, much like the index at the back of a book. A composite index covers two or more fields at once, so a query that filters on one field and sorts by another can be answered in a single lookup.

  Edge devices now support [composite indexes](/dql/indexing#composite-index) over multiple fields. A single composite index can accelerate queries that repeatedly filter or sort by the same combination of fields—for example, tenant and time, status and assignee, or location and category.

  The following index is designed for queries that filter tasks by `status` and sort them by `createdAt`:

  ```sql DQL theme={null}
  CREATE INDEX IF NOT EXISTS status_created_idx
  ON tasks (status, createdAt DESC)
  ```

  It can improve queries such as:

  ```sql DQL theme={null}
  SELECT * FROM tasks
  WHERE status = 'open'
  ORDER BY createdAt DESC
  ```

  Field order matters. Put fields used in equality filters first, followed by fields used for range filters or sorting. Composite indexes can also include array and object values. If you are unsure which fields to index, run [`ADVISE`](/dql/advise) against the query to get an index recommendation.

  ## Find the right indexes with ADVISE

  [`ADVISE`](/dql/advise) turns index optimization into a guided workflow. Prefix a query with `ADVISE`, and Ditto plans how it would execute that query without actually running it or reading any documents. It inspects the query's filters, sorts, projections, and joins. Then, Ditto spots the places where the engine would have to scan the whole collection. For each one, the response explains why an index would help and provides a ready-to-run `CREATE INDEX` statement.

  For example, advise a query that filters tasks by estimated effort:

  ```sql DQL theme={null}
  ADVISE SELECT * FROM tasks WHERE estimateHours > 8
  ```

  Ditto identifies the range predicate and recommends an index on `estimateHours`:

  ```json theme={null}
  {
    "advice": {
      "statement": "select * from tasks where estimateHours > 8",
      "suggestedIndexes": [
        {
          "collection": "tasks",
          "reason": "range predicates on `estimateHours`",
          "statement": "CREATE INDEX IF NOT EXISTS adv_tasks_estimateHours ON default:`tasks` (`estimateHours` ASC)"
        }
      ]
    }
  }
  ```

  Run the suggested statement yourself, or use `ADVISE AND PROVISION` to create the recommended indexes automatically. `ADVISE` can also recommend composite and covering indexes for more complex filters, sorting, projections, and joins. It is currently available on edge devices.

  ## Return changed documents with RETURNING

  [`RETURNING`](/dql/returning) lets an `INSERT`, `UPDATE`, `DELETE`, `EVICT`, or `TOMBSTONE` statement return data from the documents it changed. A data mutation and its confirmation can become one operation. Applications can receive the affected data immediately instead of issuing a second query.

  For example, update matching documents and return their IDs and new values in one operation:

  ```sql DQL theme={null}
  UPDATE tasks
  SET status = 'done'
  WHERE projectId = 'proj-42' AND status = 'open'
  RETURNING _id, status
  ```

  `RETURNING` is especially valuable with deletes and evictions because it can return document contents before they are removed. It also supports projections, expressions, aliases, and aggregates such as `RETURNING COUNT(*) AS removed`.

  Ditto 5.1 also adds [`INSERT ... SELECT`](/dql/insert#insert-from-a-select-statement) for creating documents directly from query results.

  ## Control long-running requests

  A single expensive query on an edge device can hold resources that are needed by the rest of an application. Two new [system parameters](/dql/alter-system) can catch runaway queries before they cause performance issues.

  | System parameter                | Default | Behavior                                                                  |
  | ------------------------------- | ------: | ------------------------------------------------------------------------- |
  | `DQL_SLOW_REQUEST_WARN_SECONDS` |    `60` | Logs request details at the threshold and repeats while the request runs. |
  | `DQL_REQUEST_TIMEOUT_SECONDS`   |     `0` | Cooperatively cancels requests that exceed the configured limit.          |

  Set either parameter to `0` to disable it. Request history can also [filter by request type or explicit profiling requests](/dql/virtual-collections#request-history).

  ***

  # 3. Security: Expanded Certificate Revocation

  Security policies at the edge need to keep working even when devices are not continuously connected to the cloud. Security teams lose sleep over what happens when an edge device is lost or stolen.

  SDK 5.1 will help security teams sleep better. Certificate revocation information now propagates securely from Ditto Server to edge devices and from peer to peer throughout the mesh. As peers connect, they share the latest revocation information.

  Revocation enforcement is enabled by default. Peers reject new connections that present a revoked certificate and terminate matching active connections when a revocation arrives. This isolates revoked clients and prevents them from reconnecting through another peer in the mesh.

  Every hop verifies the revocation's signature against its trusted certificate authority keys, the source that issues each device's identity credentials. A compromised peer cannot forge revocations.

  ***

  # 4. Troubleshooting: Identify Problems and Recover Automatically

  A device misbehaving in the field is a hard problem to tackle in edge computing. It's expensive and inefficient to fly an engineer out to attach a debugger to a tablet that is 3,000 miles away in the back of a restaurant.

  SDK 5.1 adds new options to remotely collect edge device data and identify the root cause of an issue. Support bundles now include `config_snapshot.json`, which records the effective `DittoConfig`, transport configuration, system parameters, and SDK version at capture time. This provides essential device config information at the start of every investigation or support case.

  Other new remote diagnostics include:

  * A configurable Unix `debug_socket` lets you remotely run DQL diagnostics against an edge device.
    * You can inspect a live device directly rather than reproducing the problem in a lab.
  * Nine new network counters under `ditto.network.dsoq.*` surface Ditto Sync over QUIC (DSOQ) protocol failures in your existing production metrics, without debug logs.
  * SQLite metrics distinguish the application data store from replication metadata databases on supported Unix platforms, showing which one is driving disk activity.

  SDK 5.1 also repairs a class of problems on its own. Corrupted per-peer replication metadata is now detected, reset, and rebuilt automatically without affecting application documents. Previously, this class of corruption could require clearing the device's local store. The result is faster diagnosis, fewer escalations, and fewer cases that end with wiping the app and reinstalling.

  ***

  # 5. Transport at Scale: Multicast Beta

  Today, Ditto's default peer-to-peer model establishes a session between every pair of edge devices. The total connection count grows with the square of the mesh size, O(N²). Six devices need 15 connections, and sixty devices need 1,770. Connection maintenance eventually becomes the dominant cost in environments with a large number of edge devices like a mall, a ship, a concert venue, or an aircraft.

  Multicast is now available in SDK 5.1 as an opt-in beta. Devices join a shared multicast group rather than pairing off, which drops the connection count from O(N²) to O(N). There is one group membership per device.

  A sender would typically transmit an update once per device in the mesh, which is O(N). Multicast enables the sender to publish a single broadcast to the group, which is approximately O(1). There is one send, no matter how many devices are listening.

  The transport is built on reliable multicast (NORM, RFC 5740) combined with Ditto's data reconciliation, so peers recover missed data and catch up after joining or reconnecting.

  When multicast is configured and available, it becomes the preferred replication path. Traffic is encrypted for the group, and existing peer-to-peer transports remain active as automatic fallback for peers the group cannot reach. Documents and attachments both replicate over multicast, including repair of missing attachment data.

  <Warning>
    Multicast is a beta capability in SDK 5.1. It ships in the core SDK as an opt-in feature rather than a part of the standard build. It is available in the Swift, Kotlin/KMP, Flutter, and Rust SDKs.

    Contact Ditto support or your Ditto representative before deploying multicast in production to understand the current beta limitations.
  </Warning>

  # Upgrading to 5.1

  The upgrade to Ditto Edge SDK 5.1 is seamless. Simply bump your dependency to 5.1.0 and Ditto handles the rest. An index migration will run automatically the first time your app starts. Data sync is backward-compatible, and 5.1 peers will work with 5.0 and v4 peers while you roll out gradually.

  ## Tested rollback compatibility

  Ditto Edge SDK 5.1 has undergone extensive backward-compatibility and rollback testing to ensure production deployments can safely return to supported earlier SDK versions when needed.

  The 5.1 SDK changes the on-disk index format. If you need to downgrade from Ditto 5.1 or later, migrate through Ditto 5.0.2+. You can also migrate through Ditto 4.14.6+. These versions recognize the updated index format and automatically revert it to the format understood by earlier versions.

  During the downgrade, composite indexes are replaced with single-field indexes, one for each of the composite index's keys.

  See [index migration and downgrade behavior](/dql/indexing#migration) for details. As with any production upgrade, validate the procedure with representative application data before deployment.
</Update>

<Update label="5.1.0 (Rust)" description="Release Date: Aug 18, 2026" tags={["SDK", "Rust"]} className="rn-upd">
  ## Rust-Specific Changes

  The `auth` module is now public, exposing the authenticator, authentication event handler, client feedback, and development-provider APIs.

  The legacy `identity` module is deprecated in favor of `auth`, and the query-builder-era `SortDirection` enum is deprecated in favor of DQL `ORDER BY` clauses.

  ## Rust Specific Changelog

  <Icon icon="plus" iconType="solid" horizontal /> **Added**:

  * The `auth` module is now public, exposing `DittoAuthenticator`, `DittoAuthenticationEventHandler`, `AuthenticationClientFeedback`, and `get_development_provider()`. (#SDKS-3022)
  * `MulticastBetaConfig` and `PeerToPeer::multicast_beta` for configuring the beta reliable UDP multicast transport. The transport is disabled by default, with availability limited to supported platforms during the beta. On Android and iOS, changes requested while sync is active take effect after sync is stopped and successfully started again, when Ditto validates platform prerequisites. (#SDKS-4471)
  * `ConnectionType::Multicast` enum option representing beta reliable UDP multicast connections. Transport availability is limited to supported platforms during the beta. (#SDKS-4471)

  <Icon icon="rotate-reverse" iconType="solid" horizontal /> **Changed**:

  * Added suggested actions to the warning logged when the key in attestation does not match the key reported by transport. (#21402)
  * Log lines for update payloads now report `chunk_size` instead of `data` to clarify the meaning of the field. (#22102)

  <Icon icon="triangle-exclamation" iconType="solid" horizontal /> **Deprecated**:

  * The `SortDirection` enum in the `Store` module, a remnant of the legacy v4 Query Builder API. Use DQL `ORDER BY` clauses instead. (#SDKS-2772)
  * The `identity` module. Use the `auth` module instead. (#SDKS-3022)
</Update>

<Update label="5.0.3" description="Release Date: Jul 22, 2026" tags={["SDK", "SDK-Common"]} className="rn-upd">
  <Icon icon="plus" iconType="solid" horizontal /> **Added**: a new system parameter transports\_websocket\_watchdog\_interval\_secs to adjust WebSocket client watchdog. (#21885)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed**: Corrected a formalisation bug causing DQL statement filters to never match, that was affecting observers using projections. (#QE-1056)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed**: Corrected a hang when accessing `system:data_sync_info`. (#QE-1095)
</Update>

<Update label="5.0.2" description="Release Date: Jun 23, 2026" tags={["SDK", "SDK-Common"]} className="rn-upd">
  <Icon icon="plus" iconType="solid" horizontal /> **Added**: a new timeout for WebSocket connect that is adjustable by system parameter transport\_websocket\_connect\_timeout. (#21866)

  <Icon icon="rotate-reverse" iconType="solid" horizontal /> **Changed**: Mesh chooser now enforces per-transport connection limits instead of a shared WiFi radio budget, preventing one transport (e.g. TCP) from starving others (e.g. AWDL, WiFi Aware). When a transport is at capacity, the behavior for new inbound connections is configurable: reject, drop oldest, drop newest, or accept. Peers at capacity reject inbound connections with a new `ConnectError::AtCapacity` variant and back off exponentially to avoid retry storms. (#NETW-1586)

  <Icon icon="plus" iconType="solid" horizontal /> **Added**: Automatic downgrading of the SP store from V3 to V2 on start-up. (#QE-595)

  <Icon icon="plus" iconType="solid" horizontal /> **Added**: Automatic downgrading of the SQLite3 schema from V3 to V2 allowing for downgrading from version 5.1. (#QE-595)

  <Icon icon="plus" iconType="solid" horizontal /> **Added**: `disable_replication_gc_on_evict` system parameter (default `false`). When set to `true`, calls to `evict()` no longer trigger immediate per-peer metadata cleanup; the periodic background replication GC continues to reclaim metadata for disconnected peers once they exceed the TTL (\~7 days by default). Intended as an opt-in escape hatch for deployments where eviction-time filesystem work contributes to write-path latency. (#QE-686)

  <Icon icon="plus" iconType="solid" horizontal /> **Added**: Optional background task to reclaim unused space in the Small Peer store and record space usage metrics. (#QE-779)

  <Icon icon="rotate-reverse" iconType="solid" horizontal /> **Changed**: The way query profile timing and count data is recorded to reduce overheads. (#QE-811)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed**: A bug in internal subscription bookkeeping caused long-connected Document Sync sessions to become disabled due to spurious capacity errors. (#SPO-1011)

  <Icon icon="plus" iconType="solid" horizontal /> **Added**: The option to set the `DITTO_SQLITE3_MAX_CONNECTIONS` parameter lower than `32`, down until `16` (#SPO-668)

  <Icon icon="rotate-reverse" iconType="solid" horizontal /> **Changed**: The default value of `SQLITE3_MAX_CONNECTIONS` parameter to 32, down from 60 (#SPO-668).

  <Icon icon="rotate-reverse" iconType="solid" horizontal /> **Changed**: The replication\_session\_request\_timeout\_secs and blob\_session\_request\_timeout\_secs system parameters are no longer functional; the timeouts have been removed. They are accepted for backwards compatibility but have no effect. (#SPO-869)
</Update>

<Update label="5.0.1" description="Release Date: May 29, 2026" tags={["SDK", "SDK-Common"]} className="rn-upd">
  <Icon icon="bin-recycle" iconType="solid" horizontal /> **Removed**: Spurious `dsoq.cbor` CBOR warning log during auth client initialization. (#21646)

  <Icon icon="plus" iconType="solid" horizontal /> **Added**: Garbage collection for document sync sessions now imposes limits on the number of disconnected sessions that will be retained, even if the TTL is not exceeded. (#DS-1065)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed**: the query engines erroneously build index spans not including the higher end for the BETWEEN operator. Fixed to include it. (#QE-896)
</Update>

<Update label="5.0.0" description="Release Date: May 5, 2026" tags={["SDK", "SDK-Common"]} className="rn-upd">
  \_*SDK 5.0 introduced DQL for all data operations and major core, performance, and upgrade changes. Shared by all SDK platforms; platform filters show that platform's own blocks.*

  # DQL for All Data Operations

  ## DQL for All Data Operations

  Ditto 5.0 completes the transition to DQL (Ditto Query Language) as the single API for all data operations. The legacy query builder has been removed.

  ### What Changed

  * **Legacy query builder removed**: All `store.collection()` methods and fluent query APIs are no longer available
  * **Full feature parity**: Every legacy operation has a DQL equivalent
  * **Single query language**: DQL handles reads, writes, subscriptions, and observers
  * **Works everywhere**: Same syntax across mobile, server, and web
  * **SQL-familiar**: Standard SQL patterns for immediate productivity

  ### Why One Query Language

  A unified query language means one implementation to maintain, faster feature delivery, and consistent behavior across all platforms. New capabilities and optimizations benefit every SDK simultaneously.

  # Core Improvements

  ## Simplified Initialization & Configuration

  Ditto 5.0 introduces a completely redesigned initialization flow built around the new `DittoConfig` pattern. This replaces the previous `Identity`-based approach with a clearer, more predictable setup that aligns with modern best practices.

  ### What's New

  The new configuration system provides:

  * **Unified configuration object**: All initialization parameters are set through `DittoConfig` factory methods
  * **Fallible initialization**: Explicit error handling during setup catches configuration issues early
  * **Asynchronous patterns**: Native async/await support where appropriate for each platform
  * **Simplified authentication**: Clearer authorization client that's easier to understand and implement

  ### What This Solves

  The previous initialization flow required multiple unintuitive settings due to legacy compatibility concerns. For example, developers had to set `disableCloudSync = true` to connect to a Big Peer, which was confusing and non-obvious.

  The new pattern consolidates all configuration into a single, coherent flow that makes the relationship between settings explicit and easier to reason about.

  ### Accessing Configuration at Runtime

  After initializing Ditto, you can access the configuration object to retrieve settings like the database ID. This replaces methods like `getAppID()` from v4:

  ```javascript theme={null}
  // Example: Access database ID
  const config = ditto.config;  // or ditto.getConfig() depending on SDK
  const databaseID = config.databaseID;
  ```

  Each SDK provides access to the config object with language-appropriate naming conventions. See the SDK-specific migration guides for exact syntax.

  ### Availability

  The `DittoConfig` pattern was introduced as an option in v4.12 and becomes the only initialization method in v5.0.

  <Note>
    Migration guides for each SDK are available in the v5 documentation. If you're currently on v4.11 or later, the migration path is straightforward.
  </Note>

  ## Schema-Free Data Modeling

  Ditto 5.0 transforms the developer experience by making DQL strict mode disabled by default. This eliminates the need to define collection schemas or CRDT types upfront, allowing you to insert nested objects and data structures without pre-defining types.

  ### What's New

  With strict mode disabled by default, Ditto changes how objects are stored and synchronized:

  **Objects default to MAP type instead of REGISTER type**

  This fundamental change means:

  * **Field-level sync**: Ditto syncs individual field changes instead of replacing entire objects
  * **Automatic type inference**: No need to pre-define collection schemas or CRDT types
  * **Nested structures**: Insert complex JSON-like documents without type definitions
  * **Concurrent updates merge**: When peers update different fields simultaneously, both changes are preserved

  For example, if two peers update different fields in the same object concurrently, both changes sync successfully rather than one overwriting the other.

  ### What This Solves

  This change provides a more simplified data management structure with two key benefits:

  * **Add-wins behavior on objects**: When peers create or modify objects, additions are preserved rather than overwritten
  * **Field-level delta sync on all nodes**: Every field in your document syncs independently, reducing bandwidth and enabling fine-grained conflict resolution throughout the entire document structure

  ### New Customers

  Schema-free data modeling is enabled by default in Ditto 5.0. No action is needed—simply start building with automatic type inference and field-level sync.

  ### Migrating Customers

  <Warning>
    If you're migrating from Ditto 4.X, set `DQL_STRICT_MODE = true` to ensure your application behavior remains the same:
  </Warning>

  ```sql theme={null}
  ALTER SYSTEM SET DQL_STRICT_MODE = true
  ```

  This maintains the same data modeling semantics you're currently using. Once your application is stable on v5, follow the strict mode migration guide and work with the Ditto CX team to migrate to schema-free data modeling and take advantage of the improved developer experience.

  ### Migration Considerations

  Strict mode is a **local configuration setting** on each device that controls how DQL interprets and writes data structures.

  **How it works across peers:**

  * Data **syncs successfully** between peers regardless of different strict mode settings
  * Each peer interprets data based on its **own** strict mode setting when reading/writing
  * With `DQL_STRICT_MODE=false`: Objects are inferred as MAPs (field-level merging)
  * With `DQL_STRICT_MODE=true`: Objects without explicit type definitions are treated as REGISTERs (whole object replacement)

  **Impact on data:**

  * Collections/documents created, modified, or read while strict mode is **disabled** will use inferred types (objects → MAPs by default)
  * Collections/documents created, modified, or read while strict mode is **enabled** use default REGISTER type and require explicit definitions for other types

  **Best practices for mixed deployments:**

  * If mixing settings, explicitly define MAP types in collection definitions on peers with strict mode enabled

  <Note>
    Learn more about strict mode, cross-peer synchronization, and troubleshooting in the [DQL Strict Mode documentation](/dql/strict-mode).
  </Note>

  ## New DQL Query Features

  Ditto 5.0 introduces several new DQL syntax features that expand query capabilities and make complex queries easier to write.

  ### CASE Statements

  Add conditional logic to queries with CASE expressions:

  <CodeGroup>
    ```sql Simple CASE theme={null}
    -- Match a field against multiple values
    SELECT CASE a
      WHEN 1 THEN 'one'
      WHEN 2 THEN 'two'
      ELSE 'neither'
    END
    FROM test
    ```

    ```sql Searched CASE theme={null}
    -- Use conditional expressions
    SELECT CASE
      WHEN a = 1 THEN 'one'
      WHEN a = 2 THEN 'two'
      ELSE 'neither'
    END
    FROM test
    ```
  </CodeGroup>

  ### BETWEEN Expressions

  The `BETWEEN` operator provides a shorthand for defining an inclusive numeric range for an expression result.

  **Syntax:**

  ```sql theme={null}
  <expr> BETWEEN <low-expr> AND <high-expr>
  ```

  **Example:**

  ```sql theme={null}
  SELECT * FROM test WHERE a BETWEEN 1 AND 10
  ```

  This is equivalent to `(a >= 1 AND a <= 10)`.

  <Warning>
    **Order matters**: The order of expressions is important. Reversing the terms implies an inverse range - `BETWEEN 1 AND 10` and `BETWEEN 10 AND 1` are NOT equivalent.
  </Warning>

  ### Array & Object Search Syntax

  Test elements within arrays or objects using `ANY`, `EVERY`, or `ANY AND EVERY` operators:

  **Syntax:**

  ```sql theme={null}
  (ANY|EVERY|ANY AND EVERY) [name:] value (IN|WITHIN) expression SATISFIES condition END
  ```

  * **IN** - Searches the array/object directly
  * **WITHIN** - Searches recursively through nested structures
  * **ANY** - Returns true if at least one element matches
  * **EVERY** - Returns true if all elements match (empty arrays/objects pass)
  * **ANY AND EVERY** - Like EVERY, but empty arrays/objects fail

  <CodeGroup>
    ```sql Arrays theme={null}
    -- Check if any element in an array equals 2
    SELECT ANY x IN [1,2,3] SATISFIES x = 2 END
    FROM system:dual

    -- Check if any nested element equals 2
    SELECT ANY x WITHIN [1,[2],3] SATISFIES x = 2 END
    FROM system:dual

    -- Check if all elements equal 1 (or are arrays)
    SELECT EVERY x WITHIN [1,[1],1]
    SATISFIES x = 1 OR type(x) = 'array' END
    FROM system:dual
    ```

    ```sql Objects theme={null}
    -- Check if any field value equals 2
    SELECT ANY x IN {'a':1,'b':2,'c':3} SATISFIES x = 2 END
    FROM system:dual

    -- Check if any nested field value equals 2
    SELECT ANY x WITHIN {'a':1,'b':{'d':2},'c':3} SATISFIES x = 2 END
    FROM system:dual
    ```

    ```sql Practical Examples theme={null}
    -- Find documents where first array element is less than zero
    SELECT * FROM test
    WHERE ANY n:v IN test.array_field SATISFIES n = 0 AND v < 0 END

    -- Find documents where any nested field is NULL
    SELECT * FROM test
    WHERE ANY v WITHIN test.details SATISFIES v IS NULL END

    -- Find orders where items were modified after order date
    SELECT * FROM orders o
    WHERE ANY v IN o.items SATISFIES v.modified > o.order_date END
    ```
  </CodeGroup>

  ### Array & Object Transformation Syntax

  Create new arrays and objects by transforming existing data structures:

  **Array Transformation Syntax:**

  ```sql theme={null}
  ARRAY valueExpr FOR [name:] value IN source [WHEN condition] END
  ```

  **Object Transformation Syntax:**

  ```sql theme={null}
  OBJECT nameExpr:valueExpr FOR name:value IN source [WHEN condition] END
  ```

  <CodeGroup>
    ```sql Array Transformations theme={null}
    -- Transform array elements to objects with index
    ARRAY {"index":i,"val":v} FOR i:v IN [1,2,3] WHEN v%2 = 0 END
    -- Result: [{"index":1,"val":2}]

    -- Convert numbers to strings, excluding value 1
    ARRAY cast(v,'string') FOR v IN [1,2,3] WHEN v != 1 END
    -- Result: ["2","3"]

    -- Transform string characters with conditional logic
    ARRAY CASE WHEN i%2 = 0 THEN "foo" ELSE "bar" END
    FOR i:v IN split("hello","") END
    -- Result: ["foo","bar","foo","bar","foo","bar"]

    -- Extract values from object into array
    ARRAY v FOR n:v IN {"a":"one","b":"two"} END
    -- Result: ["one","two"]
    ```

    ```sql Object Transformations theme={null}
    -- Transform object keys to uppercase, filter by value length
    OBJECT upper(n):v FOR n:v IN {"a":"one","b":"two","c":"three"}
    WHEN len(v) = 3 END
    -- Result: {"A":"one","B":"two"}

    -- Convert array to object with generated field names
    OBJECT "field_"||cast(i,"string"):v FOR i:v IN [1,2,3] END
    -- Result: {"field_0":1,"field_1":2,"field_2":3}

    -- Filter object by value type
    OBJECT n:v FOR n:v IN {"a":1,"b":[],"c":2}
    WHEN type(v) != 'array' END
    -- Result: {"a":1,"c":2}
    ```
  </CodeGroup>

  **Key behaviors:**

  * If `source` evaluates to `MISSING`, the result is `MISSING`
  * For arrays: if `source` is not an array/object, the result is `NULL`
  * For objects: duplicate field names will overwrite previous values
  * Elements/fields where `valueExpr` evaluates to `MISSING` are excluded from the result

  ### Extended String Literals

  Support for escape sequences in strings:

  ```sql theme={null}
  -- Use escape sequences in string literals
  SELECT * FROM messages
  WHERE content = 'Line 1\nLine 2\tTabbed'

  -- [PLACEHOLDER: Add more escape sequence examples if needed]
  ```

  ### Hexadecimal Numeric Constants

  Additional numeric literal formats:

  ```sql theme={null}
  -- Use hexadecimal literals
  SELECT * FROM config
  WHERE flags = 0xFF

  -- [PLACEHOLDER: Add more hex literal examples if needed]
  ```

  # Performance & Platform

  ## DQL Query Performance Improvements

  Your applications will feel noticeably faster and more responsive. Data queries complete faster, delivering a snappier user experience whether users are searching, filtering, or loading content. These performance gains happen automatically—no code changes required.

  ### Query Planner Enhancements

  The DQL query planner has been enhanced to recognize more optimization opportunities:

  * **Automatic ID scan conversion**: Equality filters on `_id` fields automatically convert to ID scans, bypassing index lookups when exact document IDs are known
  * **Deferred document fetching**: Query planner can defer fetching full documents until after sorting and applying offset/limit when index access supports it
  * **Improved covering index support**: More scenarios where the query planner can satisfy queries entirely from index data without retrieving documents
  * **Index-only queries**: Additional cases where queries can be answered using only index scans

  ### Streaming Query Execution

  The query engine now uses streaming interfaces internally:

  * **Reduced memory overhead**: Results are streamed rather than fully materialized where possible
  * **DISTINCT operator streaming**: DISTINCT queries now stream results, reducing memory usage for large result sets
  * **Improved operator inlining**: Query operators can be inlined into producers for better performance

  ### Shared Statement Cache

  Ditto 5.0 introduces a shared statement cache that stores and reuses compiled query plans:

  * **Plan reuse**: Compiled query plans are cached and reused for identical or similar statements, eliminating redundant parsing and planning overhead
  * **Automatic validation**: Cached plans are automatically verified and invalidated when collection schemas or system directives change
  * **Dynamic sizing**: The cache automatically resizes based on workload patterns
  * **Improved large statement performance**: Particularly benefits complex queries and larger statements by avoiding expensive re-compilation

  This optimization is especially impactful for applications that execute the same queries repeatedly, such as real-time dashboards or frequently-accessed data views.

  <Note>
    These improvements are automatic - no code changes required. Your existing DQL queries will benefit from the enhanced query planner.
  </Note>

  ## Data Sync Performance Improvements

  Building on the performance improvements delivered in v4.13 & v4.14, Ditto 5.0 further optimizes data synchronization through tiered blob storage and protocol enhancements, making sync operations faster and more efficient.

  ### What's New

  Version 5.0 introduces:

  * **Tiered blob storage**: Smaller sync updates avoid unnecessary disk I/O by using optimized storage tiers
  * **Improved session handling**: Better avoidance of session resets on reconnection when in-flight updates were lost
  * **Optimized fsync policy**: Document sync avoids forcing files to disk by default, decreasing I/O and improving latency

  ### Impact

  Applications upgrading to v5.0 will experience:

  * **Faster sync operations**: Reduced disk I/O overhead leads to quicker data synchronization
  * **Lower latency**: Optimized file handling decreases sync latency across the board
  * **Better reconnection handling**: Fewer redundant updates after temporary disconnections
  * **Improved efficiency**: Reduced disk operations lower memory and CPU pressure during sync

  ## Local System Observability

  Gain unprecedented visibility into how Ditto operates on your devices. Query real-time metrics, inspect runtime configuration, and monitor system health directly using DQL—giving you deeper insights than ever before to debug issues, optimize performance, and understand your application's behavior.

  ### New Virtual Collections

  **`system:metrics`**

  * Query performance metrics and diagnostics in real-time
  * Access counters, timers, and other operational metrics via DQL
  * Monitor system health and performance without external tools

  **`system:system_info`**

  * Query `peer_key`, `database_id`, and configuration settings
  * Inspect runtime configuration and system parameters
  * Useful for debugging and operational awareness

  **`system:shared_statements`**

  * Inspect the query plan cache
  * View cached statements and their execution plans
  * Supports DELETE operations to clear specific cached statements
  * Helps optimize query performance and troubleshoot query planning

  <Note>
    **Local-Only Collections**: System collections are local to each peer and are **not replicated** across the mesh. To query these collections on remote peers, use [Remote Query](https://docs.ditto.live/cloud/common/operations/remote-query) from the Ditto Portal.
  </Note>

  ### Usage Example

  ```sql theme={null}
  -- Query system information
  SELECT * FROM system:system_info

  -- View cached query statements
  SELECT * FROM system:shared_statements

  -- Access metrics
  SELECT * FROM system:metrics
  WHERE metric_name = 'sync.documents_synced'

  -- Clear a specific cached statement
  DELETE FROM system:shared_statements
  WHERE statement_id = 'abc123'
  ```

  These collections provide unprecedented visibility into Ditto's internal state, making it easier to monitor, debug, and optimize your applications.

  ## Additional Improvements

  ### Reliability & Error Handling

  * **Enhanced diagnostics**: Improved logging when peers receive data that cannot be deserialized
  * **Recovery mechanisms**: Additional recovery paths for document deserialization errors
  * **Smart log levels**: Connection failures start at warning level, escalate to error only after repeated failures
  * **Panic messages**: Filtered to remove internal Rust machinery frames for improved readability

  ### Networking Improvements

  * **Graceful shutdown**: Network connections close cleanly when Ditto is stopped
  * **Faster disconnection detection**: When a peer crashes, Ditto stops attempting to connect within 15 seconds (previously up to 75 minutes)
  * **mDNS improvements**: More reliable mDNS discovery, configurable service names, better address filtering
  * **BLE improvements**: Fixed connection issues on Android 9 and earlier devices
  * **WebSocket BYOD support**: Bring Your Own Discovery now supports WebSocket connections
  * **Connection cleanup**: Fixed deadlock where devices could fail to establish new P2P connections until restarted

  ### DQL Engine Improvements

  Beyond the query performance improvements detailed above, v5 includes:

  * **Better error messages**: Improved parser error messages for invalid DQL syntax
  * **Transaction safety**: Fixed deadlock scenarios in concurrent transactions
  * **Index correctness**: Fixed issues where index scans could yield incorrect results on document deletion

  ### Logging & Diagnostics

  * **Better disk utilization**: On-disk logs resume writing to incomplete files, making better use of available space
  * **Compressed size limits**: Log file limits now apply to compressed size, significantly increasing retention
  * **Explicit flushing**: Logs explicitly flushed before aborting due to panic
  * **Virtual collections**: New `system:metrics` and `system:system_info` collections for DQL access to metrics and system information

  ### Platform Support

  * **Linux aarch64**: Kotlin SDK now supports ARM64 Linux (Raspberry Pi, AWS Graviton, etc.)
  * **Swift 6**: Full Swift 6 support with Sendable conformance
  * **16KB alignment**: React Native Android meets Google Play's November 2025 requirement

  # Upgrading to v5

  ## Terminology Updates

  Ditto 5.0 updates terminology across the platform to align with industry standards and reduce confusion.

  ### Database ID (formerly App ID)

  * `appID` → `databaseID` in all configuration methods
  * `getAppId()` → `getConfig().databaseId` in SDK APIs
  * Portal and documentation updated to use "Database ID" terminology

  **Why this matters**: The term "App ID" caused confusion, particularly for mobile developers who associate "app" with the mobile application itself rather than the Ditto database instance. "Database ID" more accurately describes what the identifier represents: a unique identifier for your Ditto database that persists across all clients.

  ### Ditto Server (formerly Ditto Cloud)

  * `isConnectedToDittoCloud` → `isConnectedToDittoServer` in presence APIs
  * Documentation updated to use "Ditto Server" terminology

  **Why this matters**: This clarifies that the property indicates connection to any Big Peer (Ditto Server), not just those running in Ditto's cloud service. This is more accurate for deployments using self-hosted Big Peers.

  ## Breaking Changes

  Ditto 5.0 is a major version release that removes deprecated APIs and legacy features. For migration guidance, see [Migration Guidance](#v5-migration-guidance).

  ### Removed APIs

  #### Legacy Query Builder (All SDKs)

  All legacy query builder APIs have been removed:

  * `store.collection()` → Use DQL `INSERT`, `UPDATE`, `EVICT` statements
  * `collection.find()` → Use DQL `SELECT` queries
  * `collection.findById()` → Use DQL with `_id` filter
  * Live queries → Use DQL observers with `store.registerObserver()`
  * Write transactions → Use `store.transaction()` with DQL

  #### Legacy Initialization (All SDKs)

  * `Identity` classes and all subclasses removed
  * `Ditto(identity:, persistenceDirectory:)` constructors removed
  * Use `DittoConfig` factory methods and `Ditto.open()` instead

  #### Sync Methods Moved

  * `ditto.startSync()` → `ditto.sync.start()`
  * `ditto.stopSync()` → `ditto.sync.stop()`
  * `ditto.isSyncActive` → `ditto.sync.isActive`

  #### Other Removals

  * `disableSyncWithV3()` - no longer needed, v3 sync removed entirely
  * `AttachmentToken` - use dictionary variant
  * Transport diagnostics APIs - obsolete, removed
  * Various deprecated presence properties (`queryOverlapGroup`, `meshRole`, etc.)
  * Emoji log level headings - setting had no effect, removed

  ### Behavioral Changes

  Several default behaviors have changed in v5:

  * **DQL strict mode**: Now defaults to `false` - no schema definitions required, automatic CRDT type inference
  * **String literals in DQL**: Double quotes now delimit strings (not identifiers) for JSON compatibility
  * **Subscription queries**: Reject `LIMIT` and `ORDER BY` unless `DQL_RESTRICT_SUBSCRIPTION=false`
  * **Observer ordering**: Observers require explicit `ORDER BY` clause for stable ordering
  * **WebSocket sync**: Disabled by default in new `TransportConfig` instances - must explicitly enable
  * **Document IDs**: `null` is no longer allowed as a document ID

  <Note>
    These behavioral changes may affect existing code. Review your DQL queries and subscription logic when migrating to v5.
  </Note>

  ### SDK Size Reduction

  The removal of legacy APIs has reduced SDK footprint by approximately 25%, resulting in:

  * Smaller application binary sizes
  * Reduced memory usage
  * Faster SDK initialization
  * Simpler maintenance and debugging
</Update>

<Update label="5.0.0 (Rust)" description="Release Date: May 5, 2026" tags={["SDK", "Rust"]} className="rn-upd">
  ### Rust Migration Guide

  Upgrading to Ditto 5.0 requires updating your initialization code and migrating from legacy query APIs to DQL. The migration process involves:

  * Updating from `DittoIdentity` to `DittoConfig`-based initialization
  * Replacing legacy query builder operations with DQL statements
  * Migrating collection observers to DQL observers
  * Updating authentication patterns

  For comprehensive migration instructions, code examples, and best practices, see the [Rust v4 to v5 Migration Guide](/sdk/latest/migration-guides/rust-v4).

  ## Rust-Specific Changes

  The Rust SDK has additional platform-specific changes in v5.0 beyond the common breaking changes.

  ### API Changes

  **Query Arguments:**

  * Added `query_arguments_cbor_data()` and `query_arguments_json_str()` methods to `SyncSubscription`
  * Use these methods to deserialize query arguments into specific types

  **Peer & Connection Properties:**

  * Renamed `Peer::peer_key_string` to `Peer::peer_key`
  * Renamed `Connection::peer_key_string1` and `Connection::peer_key_string2` to `Connection::peer1` and `Connection::peer2`
  * Renamed `ConnectionRequest::peer_key_string()` to `ConnectionRequest::peer_key()`
  * Renamed `is_connected_to_ditto_cloud` to `is_connected_to_ditto_server`

  **Sync Methods:**

  * Added `Sync::start()` as replacement for `Ditto::start_sync()`
  * Removed `Ditto::start_sync()` - Use `ditto.sync().start()` instead
  * Added `Sync::stop()` as replacement for `Ditto::stop_sync()`
  * Removed `Ditto::stop_sync()` - Use `ditto.sync().stop()` instead
  * Added `Sync::is_active()` as replacement for `Ditto::is_sync_active()`
  * Removed `Ditto::is_sync_active()` - Use `ditto.sync().is_active()` instead

  **Store Observer:**

  * Removed `Store::register_observer()` - Use `Store::register_observer_v2()` instead
  * Removed `Store::register_observer_with_signal_next()` - Use `Store::register_observer_v2()` instead

  **Store Methods:**

  * Removed `Store::execute()` - Use `Store::execute_v2()` instead
  * Removed `Store::collection_names()` - Use DQL query `SELECT * FROM system:collections` instead
  * Removed `Store::disk_usage()` - Use `Ditto::disk_usage()` instead

  **Presence:**

  * Removed `Presence::observe()` - Use `Presence::register_observer()` instead
  * Removed `Presence::exec()` - Use `Presence::graph()` instead

  **Ditto Instance Methods:**

  * Removed `Ditto::current_transport_config()` - Use `Ditto::transport_config()` instead
  * Removed `Ditto::root_dir()` - Use `Ditto::absolute_persistence_directory()` instead
  * Removed `Ditto::data_dir()` - Use `Ditto::absolute_persistence_directory()` instead
  * Removed `Ditto::authenticator()` - Use `Ditto::auth()` instead
  * Removed `app_id()` and `application_id()` - Use `config().database_id` instead

  **Version & Identity:**

  * Removed `with_sdk_version()` - Use `version()` instead
  * Removed `disable_sync_with_v3()` - Now disabled by default
  * Removed unused identity type "Manual"

  **Disk Usage API:**

  * `DiskObserverContext` renamed to `DiskUsageObserver`
  * `DiskUsage::exec()` renamed to `DiskUsage::item()`
  * `DiskUsageChild` renamed to `DiskUsageItem`

  ### Type & Naming Changes

  **Database ID:**

  * `AppId` is now called `DatabaseId`
  * This is the newtype which wraps the UUID string identifying a Ditto database

  **SiteId Removal:**

  * Removed `SiteId` type alias
  * Use `graph().local_peer.peer_key_string` instead

  ### Module Reorganization

  **Removed from Public API:**

  * `auth` module from root - Import from `identity::auth` instead
  * `ditto` module from public exports - Use types from `prelude` instead
  * `observer` module from public exports - Use specific observer types instead
  * `subscription` module from public exports - Use `sync::SyncSubscription` instead
  * `types` module from public exports - Use DQL instead

  **Removed Types:**

  * `Observer` trait - Use specific observer types returned by register\_observer methods
  * `PresenceObserver` struct from legacy API - Use `PresenceObserver` from `Presence::register_observer()`

  ### Configuration

  **DittoConfig:**

  * Updated to include missing `system_parameters` field

  **TransportConfig:**

  * Types now define `#[serde(default)]` for deserializing partial objects

  ### Logging

  **Custom Log Callbacks:**

  * Added `DittoLogger.set_custom_log_callback()` for handling Ditto log events with custom callback
  * Removed `DittoLogger::get_emoji_log_level_headings_enabled()` and `set_emoji_log_level_headings_enabled()`

  ### Authentication

  **Development Provider:**

  * Added development provider method for online playground authentication

  ### Migration Example

  The initialization flow has changed from `Identity`-based to `DittoConfig`-based patterns. Here's how to migrate:

  <CodeGroup>
    ```rust Rust - V5 theme={null}
    let config = DittoConfig::new(
        "your-database-id",  // was: app_id
        DittoConfigConnect::Server {
            url: "REPLACE_ME_WITH_YOUR_URL".parse().unwrap(),
        },
    );
    let ditto = Ditto::open(config).await?;

    // Start sync
    ditto.sync().start()?;  // was: ditto.start_sync()
    ```

    ```rust Rust - V4 theme={null}
    let identity = Identity::online_playground(
        "your-app-id",
        "your-token"
    )?;
    let ditto = Ditto::new(identity, "/path/to/data")?;

    ditto.start_sync()?;
    ```
  </CodeGroup>

  ### Accessing Configuration at Runtime

  After initialization, you can access your Ditto configuration to retrieve settings like the database ID:

  ```rust theme={null}
  let config = ditto.config();
  let database_id = &config.database_id;  // Replaces ditto.app_id() from v4
  ```

  ### Migration Path

  All legacy query operations have direct DQL equivalents:

  **Update Operations:**

  <CodeGroup>
    ```rust Rust - DQL (v5) theme={null}
    ditto.store().execute(
        "UPDATE cars SET miles = 50000 WHERE _id = 'abc123'"
    ).await?;
    ```

    ```rust Rust - Legacy Query Builder (v4) theme={null}
    ditto.store().collection("cars")
        .find_by_id("abc123")
        .update(|doc| {
            doc.at("miles").set(50000)?;
            Ok(())
        })?;
    ```
  </CodeGroup>

  **Observers:**

  <CodeGroup>
    ```rust Rust - DQL Observer (v5) theme={null}
    ditto.store().register_observer(
        "SELECT * FROM cars WHERE miles > 100000",
        move |result| {
            // handle changes
        }
    )?;
    ```

    ```rust Rust - Legacy Observer (v4) theme={null}
    ditto.store().collection("cars")
        .find("miles > 100000")
        .observe(move |docs, event| {
            // handle changes
        });
    ```
  </CodeGroup>

  <Note>
    Complete migration examples for all legacy query patterns are available in the [Legacy to DQL Migration Guide](/dql/legacy-to-dql-adoption).
  </Note>

  ### How It Works

  The key difference is whether you need to explicitly define MAP types for objects:

  <CodeGroup>
    ```rust DQL_STRICT_MODE=false (Default in v5) theme={null}
    // Objects are automatically inferred as MAPs - no definition needed
    ditto.store().execute(
        r#"
        INSERT INTO products
        DOCUMENTS ({
            _id: '123',
            name: 'Widget',
            metadata: {
                manufacturer: 'Acme Corp',
                warehouse: 'East'
            }
        })
        "#
    ).await?;

    // Query without type definitions
    ditto.store().execute("SELECT * FROM products WHERE _id = '123'").await?;
    ```

    ```rust DQL_STRICT_MODE=true (Default in v4) theme={null}
    // Must explicitly define MAP types to use objects
    ditto.store().execute_v2(
        r#"
        INSERT INTO COLLECTION products (metadata MAP)
        DOCUMENTS ({
            _id: '123',
            name: 'Widget',
            metadata: {
                manufacturer: 'Acme Corp',
                warehouse: 'East'
            }
        })
        "#,
        None
    )?;

    // Query with explicit MAP definition
    ditto.store().execute_v2(
        r#"
        SELECT * FROM COLLECTION products (metadata MAP)
        WHERE _id = '123'
        "#,
        None
    )?;
    ```
  </CodeGroup>

  ### Migration

  Update your code to use the new terminology:

  <CodeGroup>
    ```rust Rust - New Terminology (v5) theme={null}
    // Database ID
    let config = DittoConfig::new(
        "your-database-id",  // was: app_id
        DittoConfigConnect::Server {
            url: "REPLACE_ME_WITH_YOUR_URL".parse().unwrap(),
        },
    );

    // Presence API
    if peer.is_connected_to_ditto_server {
        // handle connection
    }
    ```

    ```rust Rust - Old Terminology (v4) theme={null}
    // App ID
    let identity = Identity::online_playground(
        "your-app-id",
        "your-token"
    )?;

    // Presence API
    if peer.is_connected_to_ditto_cloud() {
        // handle connection
    }
    ```
  </CodeGroup>

  The actual ID values and functionality remain unchanged - only the parameter and property names have been updated.

  ## Rust Specific Changes

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed**:

  * Updated DittoConfig to include missing `system_parameters` field in the Rust SDK (#19436)

  <Icon icon="rotate-reverse" iconType="solid" horizontal /> **Changed**:

  * Presence graph Peers have renamed the `is_connected_to_ditto_cloud` property to `is_connected_to_ditto_server` to clarify that it reflects a connection to any Big Peer rather than specifically those in the Ditto cloud service (#20302)
  * `AppId` is now called `DatabaseId`. This is the newtype which wraps the UUID string identifying a Ditto database (#20390)
  * Renamed `Peer::peer_key_string` to `Peer::peer_key` (#SDKS-1185)
  * Renamed `Connection::peer_key_string1` and `Connection::peer_key_string2` to `Connection::peer1` and `Connection::peer2` (#SDKS-1185)
  * Renamed `ConnectionRequest::peer_key_string()` to `ConnectionRequest::peer_key()` (#SDKS-1185)
  * `DiskUsageChild` renamed to `DiskUsageItem`, `DiskUsageObserverCtx` renamed to `DiskUsageObserver`, and `DiskUsage::exec()` renamed to `DiskUsage::item()`, for consistency with names in other Ditto SDKs and documentation (#SDKS-596)

  <Icon icon="plus" iconType="solid" horizontal /> **Added**:

  * `query_arguments_cbor_data` and `query_arguments_json_str` methods to `SyncSubscription` instances. If you want to deserialize query arguments into a specific type, then use these and deserialize things as required (#17003)
  * `TransportConfig` types now define `#[serde(default)]` to make deserializing transport configs with partial objects work as expected (#20162)
  * Ditto log events can be handled with a custom callback using DittoLogger.set\_custom\_log\_callback() (#20239)
  * Development provider method for online playground authentication (#20254)
  * `Sync::stop()` as a replacement for `Ditto::stop_sync()` (#SDKS-2355)
  * `Sync::start()` as a replacement for `Ditto::start_sync()` (#SDKS-2472)
  * `Sync::is_active()` as a replacement for `Ditto::is_sync_active()` (#SDKS-2473)
  * `AwdlConfig` and `WifiAwareConfig` structs to `PeerToPeer` transport configuration (#SDKS-2952)

  <Icon icon="bin-recycle" iconType="solid" horizontal /> **Removed**:

  * Collection struct. Use DQL statements instead (#17786)
  * PendingCursorOperation struct. Use DQL statements instead (#17786)
  * PendingIdSpecificOperation struct. Use DQL statements instead (#17786)
  * PendingCollectionsOperation struct. Use DQL statements instead (#17786)
  * LiveQuery struct. Use Store::register\_observer\_v2() instead (#17786)
  * LiveQueryEvent enum. Use QueryResult from Store::register\_observer\_v2() instead (#17786)
  * LiveQueryMove enum. Use Store::register\_observer\_v2() instead (#17786)
  * SingleDocumentLiveQueryEvent enum. Use Store::register\_observer\_v2() instead (#17786)
  * Subscription type alias. Use SyncSubscription from Sync::register\_subscription\_v2() instead (#17786)
  * DittoDocument trait. Use Document type from Store::execute\_v2() instead (#17786)
  * DittoMutDocument trait. Use DQL statements with Store::write() instead (#17786)
  * DittoCounter struct. Use DQL COUNTER type instead (#17786)
  * DittoMutableCounter struct. Use DQL COUNTER type instead (#17786)
  * DittoRegister struct. Use DQL REGISTER type instead (#17786)
  * DittoMutableRegister struct. Use DQL REGISTER type instead (#17786)
  * EventHandler trait. Use callbacks with Store::register\_observer\_v2() instead (#17786)
  * CollectionsEvent enum. Use DQL queries instead (#17786)
  * CollectionsEventHandler trait. Use DQL queries instead (#17786)
  * SingleDocumentEventHandler trait. Use Store::register\_observer\_v2() instead (#17786)
  * COrderByParam struct. Use ORDER BY in DQL queries instead (#17786)
  * QuerySortDirection enum. Use ASC/DESC in DQL queries instead (#17786)
  * MutableValue trait. Use DQL statements instead (#17786)
  * Store::collection() method. Use DQL statements instead (#17786)
  * Store::collections() method. Use DQL queries instead (#17786)
  * Store::with\_batched\_write() method. Use Store::write() instead (#17786)
  * Store::queries\_hash() method. Use DQL queries instead (#17786)
  * Store::queries\_hash\_mnemonic() method. Use DQL queries instead (#17786)
  * Store::register\_observer() method. Use Store::register\_observer\_v2() instead (#17786)
  * Store::register\_observer\_with\_signal\_next() method. Use Store::register\_observer\_v2() instead (#17786)
  * Store::execute() method. Use Store::execute\_v2() instead (#17786)
  * Store::collection\_names() method. Use DQL query "SELECT \* FROM system:collections" instead (#17786)
  * Store::disk\_usage() method. Use Ditto::disk\_usage() instead (#17786)
  * Store::start\_all\_live\_query\_webhooks() method. Live query webhooks feature has been deprecated (#17786)
  * Store::start\_live\_query\_webhook\_by\_id() method. Live query webhooks feature has been deprecated (#17786)
  * Store::register\_live\_query\_webhook() method. Live query webhooks feature has been deprecated (#17786)
  * Store::live\_query\_webhook\_generate\_new\_api\_secret() method. Live query webhooks feature has been deprecated (#17786)
  * Store::timeseries() method. The experimental timeseries feature has been deprecated (#17786)
  * TimeSeries struct. The experimental timeseries feature has been deprecated (#17786)
  * WriteStrategy enum. Use DQL INSERT statements instead (#17786)
  * Presence::observe() method. Use Presence::register\_observer() instead (#17786)
  * Presence::exec() method. Use Presence::graph() instead (#17786)
  * PresenceObserver struct from legacy API. Use PresenceObserver from Presence::register\_observer() instead (#17786)
  * Ditto::current\_transport\_config() method. Use Ditto::transport\_config() instead (#17786)
  * Ditto::root\_dir() method. Use Ditto::absolute\_persistence\_directory() instead (#17786)
  * Ditto::data\_dir() method. Use Ditto::absolute\_persistence\_directory() instead (#17786)
  * Ditto::authenticator() method. Use Ditto::auth() instead (#17786)
  * SiteId type alias. Use graph().local\_peer.peer\_key\_string instead (#17786)
  * TransportDiagnostics struct. This unimplemented feature has been removed (#17786)
  * Observer trait. Use specific observer types returned by register\_observer methods instead (#17786)
  * auth module from root. Import from identity::auth instead (#17786)
  * ditto module from public exports. Use types from prelude instead (#17786)
  * observer module from public exports. Use specific observer types instead (#17786)
  * subscription module from public exports. Use sync::SyncSubscription instead (#17786)
  * types module from public exports. Use DQL instead (#17786)
  * Unused identity type "Manual" has been removed (#20257)
  * Ditto struct no longer has app\_id() or application\_id() functions. Use config().database\_id instead (#20259)
  * with\_sdk\_version() method and replaced with version() method (#20298)
  * disable\_sync\_with\_v3() API. This is now disabled by default (#20325)
  * `Ditto::stop_sync()`. Use `ditto.sync().stop()` instead (#SDKS-2355)
  * `Ditto::start_sync()`. Use `ditto.sync().start()` instead (#SDKS-2472)
  * `Ditto::is_sync_active()`. Use `ditto.sync().is_active()` instead (#SDKS-2473)
  * `DittoLogger::get_emoji_log_level_headings_enabled()` and `DittoLogger::set_emoji_log_level_headings_enabled()` methods (#SDKS-2604)
  * `DittoAuthenticator::login_with_token_and_feedback()` method. Use `DittoAuthenticator::login()` instead (#SDKS-2589)
  * `DittoBuilder` struct. Use `Ditto::open()` or `Ditto::open_sync()` with `DittoConfig` for initialization instead (#SDKS-2589)
  * `Ditto::builder()` method. Use `Ditto::open()` or `Ditto::open_sync()` with `DittoConfig` instead (#SDKS-2589)
  * `Ditto::get_logging_enabled()` method. Use `DittoLogger::get_logging_enabled()` instead (#SDKS-2589)
  * `Ditto::get_minimum_log_level()` method. Use `DittoLogger::get_minimum_log_level()` instead (#SDKS-2589)
  * `Ditto::persistence_directory()` method. Use `DittoConfig` to obtain persistence directory configuration (#SDKS-2589)
  * `Ditto::set_logging_enabled()` method. Use `DittoLogger::set_logging_enabled()` instead (#SDKS-2589)
  * `Ditto::set_minimum_log_level()` method. Use `DittoLogger::set_minimum_log_level()` instead (#SDKS-2589)
  * `ErrorKind::as_str()` method. Use the `Display` implementation for `ErrorKind` instead (#SDKS-2589)
  * `Identity` trait and all concrete implementations (`OnlineWithAuthentication`, `OnlinePlayground`, `OfflinePlayground`, `SharedKey`). Use `DittoConfig` for all initialization scenarios instead (#SDKS-2589)
  * `Observer` trait. Use `drop(observer)` to stop an observer (#SDKS-2589)
</Update>

<ReleaseNotesPager />


## Related topics

- [Release Notes](/cloud/release-notes-hub.md)
