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

# What's New in v26.3

export const InternalLink = ({version, path = "", children, ...props}) => {
  let detectedVersion = version || "stable";
  if (typeof window !== 'undefined' && !version) {
    const match = window.location.pathname.match(/\/docs\/([^/]+)/);
    if (match) {
      detectedVersion = match[1];
    }
  }
  const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
  return <a href={`/docs/${detectedVersion}/${normalizedPath}`} {...props}>
      {children}
    </a>;
};

export const InlineImage = ({src, alt = "", height = "1.6em"}) => {
  return <img noZoom src={src} alt={alt} style={{
    display: "inline",
    verticalAlign: "start",
    height: height,
    margin: "0"
  }} />;
};

export const MarketoEmailForm = ({successMessage = "Thanks!", formId = 1083}) => {
  useEffect(() => {
    function initializeReleaseNotesSignup() {
      if (window.__cockroachReleaseNotesSignupInitialized) {
        if (window.__cockroachReleaseNotesRefresh) {
          window.__cockroachReleaseNotesRefresh();
        }
        return;
      }
      window.__cockroachReleaseNotesSignupInitialized = true;
      const MARKETO_BASE_URL = "https://go.cockroachlabs.com";
      const MARKETO_MUNCHKIN_ID = "350-QIN-827";
      const MARKETO_FORMS_SCRIPT = "https://go.cockroachlabs.com/js/forms2/js/forms2.min.js";
      const DEFAULT_FORM_ID = 1083;
      const EMAIL_ERROR = "Enter a valid email address.";
      const FORM_LOAD_ERROR = "Unable to load the release notes form. Disable content blockers and try again.";
      const LOCALHOST_ERROR = "Marketo rejects submissions from localhost. Test this form on a deployed preview URL.";
      const SUCCESS_MESSAGE = "Thanks!";
      const WIDGET_SELECTOR = "[data-release-notes-signup]";
      const FORM_MOUNT_ID = "cockroachReleaseNotesFormMount";
      const SUBMIT_FRAME_NAME = "cockroachReleaseNotesSubmitFrame";
      const widgetState = new WeakMap();
      let marketoScriptPromise;
      const marketoFormPromises = {};
      let activeWidget = null;
      function getWidgetFormId(widget) {
        const formId = widget && Number(widget.getAttribute("data-form-id"));
        return formId || DEFAULT_FORM_ID;
      }
      function toSecureMarketoUrl(url) {
        if (typeof url !== "string") {
          return url;
        }
        return url.replace("http://go.cockroachlabs.com", "https://go.cockroachlabs.com");
      }
      function patchMarketoFrameTransport() {
        if (window.__cockroachReleaseNotesFrameTransportPatched || typeof HTMLIFrameElement === "undefined") {
          return;
        }
        const iframeProto = HTMLIFrameElement.prototype;
        const originalSetAttribute = iframeProto.setAttribute;
        iframeProto.setAttribute = function (name, value) {
          if (name === "src") {
            value = toSecureMarketoUrl(value);
          }
          return originalSetAttribute.call(this, name, value);
        };
        const srcDescriptor = Object.getOwnPropertyDescriptor(iframeProto, "src");
        if (srcDescriptor && typeof srcDescriptor.get === "function" && typeof srcDescriptor.set === "function") {
          try {
            Object.defineProperty(iframeProto, "src", {
              configurable: true,
              enumerable: srcDescriptor.enumerable,
              get: function () {
                return srcDescriptor.get.call(this);
              },
              set: function (value) {
                srcDescriptor.set.call(this, toSecureMarketoUrl(value));
              }
            });
          } catch (error) {}
        }
        window.__cockroachReleaseNotesFrameTransportPatched = true;
      }
      function getState(widget) {
        let state = widgetState.get(widget);
        if (!state) {
          state = {
            error: "",
            isSubmitted: false,
            isSubmitting: false,
            successMessage: widget.getAttribute("data-success-message") || SUCCESS_MESSAGE,
            submitTimeoutId: null
          };
          widgetState.set(widget, state);
        }
        return state;
      }
      function getElements(widget) {
        return {
          emailInput: widget.querySelector("[data-release-notes-email]"),
          error: widget.querySelector("[data-release-notes-error]"),
          errorText: widget.querySelector("[data-release-notes-error-text]"),
          submitButton: widget.querySelector("[data-release-notes-submit]"),
          success: widget.querySelector("[data-release-notes-success]"),
          successText: widget.querySelector("[data-release-notes-success-text]")
        };
      }
      function ensureFormMount() {
        let mount = document.getElementById(FORM_MOUNT_ID);
        if (mount) {
          return mount;
        }
        mount = document.createElement("div");
        mount.id = FORM_MOUNT_ID;
        mount.setAttribute("aria-hidden", "true");
        mount.style.display = "none";
        document.body.appendChild(mount);
        return mount;
      }
      function getDomForm(form) {
        const formElement = typeof form.getFormElem === "function" ? form.getFormElem() : null;
        return formElement && formElement[0] ? formElement[0] : formElement;
      }
      function mountMarketoForm(form) {
        const domForm = getDomForm(form);
        if (!domForm || domForm.isConnected) {
          return domForm;
        }
        ensureFormMount().appendChild(domForm);
        return domForm;
      }
      function applyFormValues(form, values) {
        if (typeof form.setValues === "function") {
          form.setValues(values);
          return;
        }
        if (typeof form.vals === "function") {
          form.vals(values);
        }
      }
      function ensureSubmitFrame() {
        let frame = document.querySelector('iframe[name="' + SUBMIT_FRAME_NAME + '"]');
        if (frame) {
          return frame;
        }
        frame = document.createElement("iframe");
        frame.name = SUBMIT_FRAME_NAME;
        frame.setAttribute("aria-hidden", "true");
        frame.tabIndex = -1;
        frame.style.display = "none";
        document.body.appendChild(frame);
        return frame;
      }
      function clearSubmitTimeout(widget) {
        const state = getState(widget);
        if (state.submitTimeoutId) {
          window.clearTimeout(state.submitTimeoutId);
          state.submitTimeoutId = null;
        }
      }
      function forceSecureMarketoFrame() {
        const frame = document.querySelector("#MktoForms2XDIframe");
        if (!frame) {
          return;
        }
        const currentSrc = frame.getAttribute("src") || frame.src;
        if (!currentSrc) {
          return;
        }
        const secureSrc = toSecureMarketoUrl(currentSrc);
        if (secureSrc !== currentSrc) {
          frame.setAttribute("src", secureSrc);
        }
      }
      function ensureSecureMarketoFrame() {
        patchMarketoFrameTransport();
        forceSecureMarketoFrame();
        if (window.__cockroachReleaseNotesFrameObserverInitialized || typeof MutationObserver === "undefined") {
          return;
        }
        const observer = new MutationObserver(function () {
          forceSecureMarketoFrame();
        });
        observer.observe(document.documentElement, {
          childList: true,
          subtree: true,
          attributes: true,
          attributeFilter: ["src"]
        });
        window.__cockroachReleaseNotesFrameObserverInitialized = true;
        window.__cockroachReleaseNotesFrameObserver = observer;
      }
      function render(widget) {
        if (!widget || !document.contains(widget)) {
          return;
        }
        const state = getState(widget);
        const elements = getElements(widget);
        if (elements.submitButton) {
          elements.submitButton.disabled = state.isSubmitting;
        }
        if (elements.emailInput) {
          elements.emailInput.setAttribute("aria-invalid", state.error ? "true" : "false");
        }
        if (elements.error) {
          elements.error.hidden = !state.error;
          elements.error.style.display = state.error ? "block" : "none";
        }
        if (elements.errorText) {
          elements.errorText.textContent = state.error;
        }
        if (elements.success) {
          elements.success.hidden = !state.isSubmitted;
          elements.success.style.display = state.isSubmitted ? "block" : "none";
        }
        if (elements.successText) {
          elements.successText.textContent = state.successMessage || SUCCESS_MESSAGE;
        }
      }
      function hideMarketoForm(form) {
        const formElement = form && typeof form.getFormElem === "function" ? form.getFormElem() : null;
        if (!formElement) {
          return;
        }
        if (typeof formElement.hide === "function") {
          formElement.hide();
          return;
        }
        if (formElement[0] && formElement[0].style) {
          formElement[0].style.display = "none";
          return;
        }
        if (formElement.style) {
          formElement.style.display = "none";
        }
      }
      function isValidEmail(value) {
        return (/^[^\s@]+@[^\s@]+\.[^\s@]+$/).test(value);
      }
      function isFormLoadError(error) {
        return Boolean(error && typeof error === "object" && (error.code === "marketo_load_failed" || error.code === "marketo_not_available" || error.code === "marketo_form_init_failed"));
      }
      function isLocalhost() {
        return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
      }
      function loadMarketoForms() {
        ensureSecureMarketoFrame();
        if (window.MktoForms2) {
          return Promise.resolve(window.MktoForms2);
        }
        if (!marketoScriptPromise) {
          marketoScriptPromise = new Promise(function (resolve, reject) {
            function finalizeLoad() {
              if (window.MktoForms2) {
                forceSecureMarketoFrame();
                resolve(window.MktoForms2);
                return;
              }
              marketoScriptPromise = null;
              const error = new Error("MktoForms2 did not load.");
              error.code = "marketo_not_available";
              reject(error);
            }
            function handleError() {
              marketoScriptPromise = null;
              const error = new Error("Failed to load Marketo forms.");
              error.code = "marketo_load_failed";
              reject(error);
            }
            const existingScript = document.querySelector('script[src="' + MARKETO_FORMS_SCRIPT + '"]');
            if (existingScript) {
              existingScript.addEventListener("load", finalizeLoad, {
                once: true
              });
              existingScript.addEventListener("error", handleError, {
                once: true
              });
              return;
            }
            const script = document.createElement("script");
            script.src = MARKETO_FORMS_SCRIPT;
            script.async = true;
            script.addEventListener("load", finalizeLoad, {
              once: true
            });
            script.addEventListener("error", handleError, {
              once: true
            });
            document.body.appendChild(script);
          });
        }
        return marketoScriptPromise;
      }
      function attachFormCallbacks(form) {
        if (form.__releaseNotesCallbacksAttached) {
          return;
        }
        form.__releaseNotesCallbacksAttached = true;
        form.onSuccess(function () {
          if (!activeWidget || !document.contains(activeWidget)) {
            return false;
          }
          const state = getState(activeWidget);
          clearSubmitTimeout(activeWidget);
          state.isSubmitting = false;
          state.isSubmitted = true;
          state.error = "";
          state.successMessage = activeWidget.getAttribute("data-success-message") || SUCCESS_MESSAGE;
          render(activeWidget);
          return false;
        });
        form.onValidate(function (isValid) {
          if (isValid || !activeWidget || !document.contains(activeWidget)) {
            return;
          }
          const state = getState(activeWidget);
          clearSubmitTimeout(activeWidget);
          state.isSubmitting = false;
          state.error = EMAIL_ERROR;
          render(activeWidget);
        });
      }
      function ensureForm(formId) {
        if (!marketoFormPromises[formId]) {
          marketoFormPromises[formId] = loadMarketoForms().then(function (MktoForms2) {
            const existingForm = typeof MktoForms2.getForm === "function" ? MktoForms2.getForm(formId) : null;
            if (existingForm) {
              forceSecureMarketoFrame();
              mountMarketoForm(existingForm);
              hideMarketoForm(existingForm);
              attachFormCallbacks(existingForm);
              ensureSubmitFrame();
              return existingForm;
            }
            return new Promise(function (resolve, reject) {
              MktoForms2.loadForm(MARKETO_BASE_URL, MARKETO_MUNCHKIN_ID, formId, function (form) {
                if (!form) {
                  const error = new Error("Marketo form failed to initialize.");
                  error.code = "marketo_form_init_failed";
                  reject(error);
                  return;
                }
                forceSecureMarketoFrame();
                mountMarketoForm(form);
                hideMarketoForm(form);
                attachFormCallbacks(form);
                ensureSubmitFrame();
                resolve(form);
              });
            });
          }).catch(function (error) {
            marketoFormPromises[formId] = null;
            throw error;
          });
        }
        return marketoFormPromises[formId];
      }
      function handleEmailInput(target) {
        const widget = target.closest(WIDGET_SELECTOR);
        if (!widget) {
          return;
        }
        const state = getState(widget);
        if (!state.error) {
          return;
        }
        state.error = "";
        render(widget);
      }
      function handleSubmit(button) {
        const widget = button.closest(WIDGET_SELECTOR);
        if (!widget) {
          return;
        }
        const state = getState(widget);
        const elements = getElements(widget);
        if (!elements.emailInput) {
          return;
        }
        const email = elements.emailInput.value.trim();
        if (!email) {
          state.error = EMAIL_ERROR;
          render(widget);
          return;
        }
        state.error = "";
        state.isSubmitted = false;
        state.isSubmitting = true;
        state.successMessage = widget.getAttribute("data-success-message") || SUCCESS_MESSAGE;
        render(widget);
        if (!isValidEmail(email)) {
          state.isSubmitting = false;
          state.error = EMAIL_ERROR;
          render(widget);
          return;
        }
        if (isLocalhost()) {
          state.isSubmitting = false;
          state.error = LOCALHOST_ERROR;
          render(widget);
          return;
        }
        activeWidget = widget;
        ensureForm(getWidgetFormId(widget)).then(function (form) {
          const formValues = {
            Email: email,
            Send_me_product_and_feature_updates__c: "TRUE",
            subscriptionProductUpdates: "TRUE",
            optin: "TRUE"
          };
          applyFormValues(form, formValues);
          if (typeof form.validate === "function" && !form.validate()) {
            state.isSubmitting = false;
            state.error = EMAIL_ERROR;
            render(widget);
            return;
          }
          clearSubmitTimeout(widget);
          state.submitTimeoutId = window.setTimeout(function () {
            state.isSubmitting = false;
            state.error = EMAIL_ERROR;
            render(widget);
          }, 10000);
          if (typeof form.submit !== "function") {
            throw new Error("marketo_submit_missing");
          }
          form.submit();
        }).catch(function (error) {
          clearSubmitTimeout(widget);
          state.isSubmitting = false;
          state.error = isFormLoadError(error) ? FORM_LOAD_ERROR : EMAIL_ERROR;
          render(widget);
        });
      }
      document.addEventListener("click", function (event) {
        const submitButton = event.target.closest("[data-release-notes-submit]");
        if (!submitButton) {
          return;
        }
        event.preventDefault();
        handleSubmit(submitButton);
      });
      document.addEventListener("input", function (event) {
        if (!(event.target instanceof HTMLInputElement) || !event.target.matches("[data-release-notes-email]")) {
          return;
        }
        handleEmailInput(event.target);
      });
      window.__cockroachReleaseNotesRefresh = function () {
        ensureSecureMarketoFrame();
        const widgets = document.querySelectorAll(WIDGET_SELECTOR);
        widgets.forEach(function (widget) {
          render(widget);
          ensureForm(getWidgetFormId(widget)).catch(function () {});
        });
      };
      window.__cockroachReleaseNotesRefresh();
    }
    initializeReleaseNotesSignup();
  }, []);
  return <div data-release-notes-signup data-form-id={formId} data-success-message={successMessage} className="not-prose my-4 max-w-xl">
      <div className="flex flex-col gap-3 sm:flex-row sm:items-start">
        <input data-release-notes-email type="email" inputMode="email" autoComplete="email" placeholder="Email*" aria-label="Email" className="min-w-0 flex-1 rounded-2xl border border-gray-300 bg-white px-4 py-3 text-sm text-gray-900 shadow-sm outline-none transition focus:border-primary dark:border-gray-700 dark:bg-gray-950 dark:text-white" />
        <button data-release-notes-submit type="button" className="inline-flex items-center justify-center rounded-2xl bg-primary px-5 py-3 text-sm font-semibold text-white transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-70">
          Submit
        </button>
      </div>

      <div data-release-notes-error hidden className="mt-3 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/50 dark:bg-red-950/40 dark:text-red-300">
        <span data-release-notes-error-text />
      </div>

      <div data-release-notes-success hidden className="mt-3 rounded-2xl border border-green-200 bg-green-50 px-4 py-3 text-sm font-medium text-green-800 dark:border-green-900/50 dark:bg-green-950/40 dark:text-green-200">
        <span data-release-notes-success-text>{successMessage}</span>
      </div>
    </div>;
};

export const SearchLink = ({term, children}) => {
  const handleClick = e => {
    e.preventDefault();
    const searchButton = document.querySelector("button#search-bar-entry");
    if (!searchButton) return;
    searchButton.click();
    requestAnimationFrame(() => {
      const input = document.querySelector("input#search-input");
      if (!input) return;
      const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
      nativeInputValueSetter?.call(input, term);
      input.dispatchEvent(new Event("input", {
        bubbles: true
      }));
      input.dispatchEvent(new KeyboardEvent("keydown", {
        key: "Enter",
        bubbles: true
      }));
    });
  };
  return <a href="#" onClick={handleClick}>
      {children}
    </a>;
};

CockroachDB v26.3 is an optional <InternalLink path="index#major-versions">Innovation release</InternalLink>. This version can be skipped for CockroachDB Advanced and self-hosted clusters. It is unavailable for CockroachDB Standard and CockroachDB Basic clusters.

* For a summary of the most significant changes in v26.3, refer to [Feature highlights](#feature-highlights).
* Before <InternalLink version="v26.3" path="upgrade-cockroach-version">upgrading to CockroachDB v26.3</InternalLink>, review the [breaking changes](#v26-3-0-breaking-changes), including [key cluster setting changes](#v26-3-0-key-cluster-setting-changes) and [deprecations](#v26-3-0-deprecations). Known limitations are documented alongside each feature. Refer to the feature's documentation page for details.
* For details about the support window for this release type, review the <InternalLink path="release-support-policy">Release Support Policy</InternalLink>.
* For details about all supported releases, the release schedule, and licenses, refer to <InternalLink path="index">CockroachDB Releases Overview</InternalLink>.
* After downloading a supported CockroachDB binary, learn how to <InternalLink version="stable" path="install-cockroachdb">install CockroachDB</InternalLink> or <InternalLink version="stable" path="upgrade-cockroach-version">upgrade your cluster</InternalLink>.

Get future release notes emailed to you:

<MarketoEmailForm />

## v26.3.0

Release Date: August 19, 2026

<a id="v26-3-0-downloads" />

### Downloads

<Note>
  Experimental downloads are not qualified for production use and not eligible for support or uptime SLA commitments, whether they are for testing releases or production releases.
</Note>

<table><thead><tr><th>Operating System</th><th>Architecture</th><th>Full executable</th><th>SQL-only executable</th></tr></thead><tbody><tr><td rowspan="2">Linux</td><td>Intel</td><td><a href="https://binaries.cockroachdb.com/cockroach-v26.3.0.linux-amd64.tgz">cockroach-v26.3.0.linux-amd64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-v26.3.0.linux-amd64.tgz.sha256sum">SHA256</a>)</td><td><a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.0.linux-amd64.tgz">cockroach-sql-v26.3.0.linux-amd64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.0.linux-amd64.tgz.sha256sum">SHA256</a>)</td></tr><tr><td>ARM</td><td><a href="https://binaries.cockroachdb.com/cockroach-v26.3.0.linux-arm64.tgz">cockroach-v26.3.0.linux-arm64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-v26.3.0.linux-arm64.tgz.sha256sum">SHA256</a>)</td><td><a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.0.linux-arm64.tgz">cockroach-sql-v26.3.0.linux-arm64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.0.linux-arm64.tgz.sha256sum">SHA256</a>)</td></tr><tr><td rowspan="2">Mac<br />(Experimental)</td><td>Intel</td><td><a href="https://binaries.cockroachdb.com/cockroach-v26.3.0.darwin-10.9-amd64.tgz">cockroach-v26.3.0.darwin-10.9-amd64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-v26.3.0.darwin-10.9-amd64.tgz.sha256sum">SHA256</a>)</td><td><a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.0.darwin-10.9-amd64.tgz">cockroach-sql-v26.3.0.darwin-10.9-amd64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.0.darwin-10.9-amd64.tgz.sha256sum">SHA256</a>)</td></tr><tr><td>ARM</td><td><a href="https://binaries.cockroachdb.com/cockroach-v26.3.0.darwin-11.0-arm64.tgz">cockroach-v26.3.0.darwin-11.0-arm64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-v26.3.0.darwin-11.0-arm64.tgz.sha256sum">SHA256</a>)</td><td><a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.0.darwin-11.0-arm64.tgz">cockroach-sql-v26.3.0.darwin-11.0-arm64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.0.darwin-11.0-arm64.tgz.sha256sum">SHA256</a>)</td></tr><tr><td>Windows<br />(Experimental)</td><td>Intel</td><td><a href="https://binaries.cockroachdb.com/cockroach-v26.3.0.windows-6.2-amd64.zip">cockroach-v26.3.0.windows-6.2-amd64.zip</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-v26.3.0.windows-6.2-amd64.zip.sha256sum">SHA256</a>)</td><td><a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.0.windows-6.2-amd64.zip">cockroach-sql-v26.3.0.windows-6.2-amd64.zip</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.0.windows-6.2-amd64.zip.sha256sum">SHA256</a>)</td></tr></tbody></table>

### Docker image

[Multi-platform images](https://docs.docker.com/build/building/multi-platform) include support for both Intel and ARM. Multi-platform images do not take up additional space on your Docker host.

Within the multi-platform image, both Intel and ARM images are **generally available** for production use.

To download the Docker image:

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
docker pull cockroachdb/cockroach:v26.3.0
```

<a id="v26-3-0-feature-highlights" />

### Feature highlights

This section summarizes the most significant user-facing changes in [SQL](#sql-highlights), [security](#security-highlights), [observability](#observability-highlights), [performance](#performance-highlights), and [AI](#ai-highlights).

<Tip>
  You can also search the docs for sections labeled <SearchLink term="new in v26.3">New in v26.3</SearchLink>.
</Tip>

<a id="v26-3-0-sql-highlights" />

#### SQL highlights

<table><thead><tr><th class="center-align">Feature</th><th class="center-align">Availability</th><th>Self-hosted</th><th>Basic</th><th>Standard</th><th>Advanced</th></tr></thead><tbody><tr><td><strong>Super regions with per-super-region survival goals</strong><br /><br /><InternalLink version="stable" path="multiregion-overview#super-regions">Super regions</InternalLink> are now generally available. A super region is a set of database regions that keeps all replicas of its data within the super region, primarily for data domiciling. Each super region can now have its own <InternalLink version="stable" path="alter-database#set-a-survival-goal-for-a-super-region">survival goal</InternalLink> and <InternalLink version="stable" path="alter-database#customize-a-super-region">zone configuration</InternalLink>, independent of the database default.</td><td class="icon-center">GA</td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td></tr><tr><td><strong>PostgreSQL 18 compatibility for drivers, ORMs, and tools</strong><br /><br />CockroachDB now reports PostgreSQL version 18 to clients and aligns the <InternalLink version="stable" path="pg-catalog"><code>pg\_catalog</code></InternalLink> schema with the PostgreSQL 18 system catalogs. These changes reduce errors when using current versions of drivers, ORMs, and tools. For details, refer to <InternalLink version="stable" path="postgresql-compatibility">PostgreSQL compatibility</InternalLink>.</td><td class="icon-center">GA</td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td></tr><tr><td><strong><code>DOMAIN</code> data types</strong><br /><br />CockroachDB now supports PostgreSQL-compatible <InternalLink version="stable" path="domain"><code>DOMAIN</code> data types</InternalLink>: user-defined types based on an existing data type, with optional constraints and a default value. Define a rule once and enforce it consistently across columns and tables.</td><td class="icon-center">GA</td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td></tr><tr><td><strong><code>pg\_dump</code> support for schema export</strong><br /><br />You can now use PostgreSQL 18's <code>pg\_dump</code> to export schema definitions from a CockroachDB database, either to recreate the schema in another CockroachDB database or to generate PostgreSQL-oriented schema definitions. Refer to <InternalLink version="stable" path="postgresql-compatibility#export-a-cockroachdb-schema-with-pg-dump">Export a CockroachDB schema with <code>pg\_dump</code></InternalLink>.</td><td class="icon-center">GA</td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td></tr><tr><td><strong>DDL and DCL statements in stored procedures</strong><br /><br /><InternalLink version="stable" path="stored-procedures">Stored procedures</InternalLink> now support DDL statements such as <code>CREATE TABLE</code> and <code>CREATE SCHEMA</code>, and DCL statements such as <code>GRANT</code> and <code>REVOKE</code>. Using DDL in a PL/pgSQL procedure requires <InternalLink version="stable" path="plpgsql#late-binding">late binding</InternalLink>. For supported statements and requirements, refer to <InternalLink version="stable" path="stored-procedures#ddl-and-dcl-statements">DDL and DCL statements</InternalLink>.</td><td class="icon-center">Preview</td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td></tr><tr><td><strong>Temporary tables in stored procedures</strong><br /><br />A <InternalLink version="stable" path="plpgsql">PL/pgSQL</InternalLink> <InternalLink version="stable" path="stored-procedures">stored procedure</InternalLink> body can now create a <InternalLink version="stable" path="temporary-tables">temporary table</InternalLink> and reference it in later statements of the same procedure body. The table remains available to the session after the procedure returns. Refer to <InternalLink version="stable" path="plpgsql#create-temporary-tables">Create temporary tables</InternalLink>.</td><td class="icon-center">Preview</td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td></tr><tr><td><strong>Transaction-scoped advisory locks</strong><br /><br />CockroachDB now supports PostgreSQL transaction-scoped advisory lock functions, such as <code>pg\_advisory\_xact\_lock</code>. A lock is tied to the transaction that acquires it and is released when the transaction commits or rolls back. Refer to <InternalLink version="stable" path="postgresql-compatibility#advisory-locks">Advisory locks</InternalLink>.</td><td class="icon-center">Preview</td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td></tr></tbody></table>

<a id="v26-3-0-security-highlights" />

#### Security highlights

<table><thead><tr><th class="center-align">Feature</th><th class="center-align">Availability</th><th>Self-hosted</th><th>Basic</th><th>Standard</th><th>Advanced</th></tr></thead><tbody><tr><td><strong>Granular RBAC for DB Console access</strong><br /><br />Non-admin users can now be granted scoped, read-only access to the DB Console <strong>Schedules</strong> and <strong>Events</strong> pages without the <code>admin</code> role: the <code>VIEWJOB</code> privilege now covers schedules, and the new <code>VIEWEVENTLOG</code> privilege grants read-only access to cluster events. Refer to <InternalLink version="stable" path="ui-overview#role-based-security">Role-based security</InternalLink>.</td><td class="icon-center">GA</td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Gray circle with horizontal white line (No)" src="https://upload.wikimedia.org/wikipedia/commons/b/b9/Eo_circle_grey_white_no-entry.svg" /></td><td class="icon-center"><InlineImage alt="Gray circle with horizontal white line (No)" src="https://upload.wikimedia.org/wikipedia/commons/b/b9/Eo_circle_grey_white_no-entry.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td></tr></tbody></table>

<a id="v26-3-0-observability-highlights" />

#### Observability highlights

<table><thead><tr><th class="center-align">Feature</th><th class="center-align">Availability</th><th>Self-hosted</th><th>Basic</th><th>Standard</th><th>Advanced</th></tr></thead><tbody><tr><td><strong>Observability into UDF and stored procedure execution</strong><br /><br /><InternalLink version="stable" path="explain-analyze"><code>EXPLAIN ANALYZE</code></InternalLink> now shows the body plans and execution statistics of <InternalLink version="stable" path="user-defined-functions">user-defined functions</InternalLink> and <InternalLink version="stable" path="stored-procedures">stored procedures</InternalLink> invoked by a statement, and statement bundles render routine bodies inline. Refer to <InternalLink version="stable" path="explain-analyze#routine-body-plans">Routine body plans</InternalLink>.</td><td class="icon-center">GA</td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td></tr><tr><td><strong>Active Session History on by default</strong><br /><br /><InternalLink version="stable" path="active-session-history">Active Session History (ASH)</InternalLink> now samples cluster activity by default, capturing per-second snapshots of active queries, jobs, and system tasks. Samples are persisted for 7 days and can be queried with SQL, with optional <InternalLink version="stable" path="active-session-history#sample-enrichment">per-execution enrichment</InternalLink>.</td><td class="icon-center">GA</td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td></tr></tbody></table>

<a id="v26-3-0-performance-highlights" />

#### Performance highlights

<table><thead><tr><th class="center-align">Feature</th><th class="center-align">Availability</th><th>Self-hosted</th><th>Basic</th><th>Standard</th><th>Advanced</th></tr></thead><tbody><tr><td><strong>SQL plan pinning</strong><br /><br />You can now <InternalLink version="stable" path="cost-based-optimizer#pin-query-plans">pin a known-good query plan</InternalLink> to a statement fingerprint so that the <InternalLink version="stable" path="cost-based-optimizer">cost-based optimizer</InternalLink> continues to choose that plan, protecting against plan regressions after statistics, schema, or version changes. Pinned plans are a type of <InternalLink version="stable" path="cost-based-optimizer#statement-hints">statement hint</InternalLink>.</td><td class="icon-center">Preview</td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td></tr></tbody></table>

<a id="v26-3-0-ai-highlights" />

#### AI highlights

<table><thead><tr><th class="center-align">Feature</th><th class="center-align">Availability</th><th>Self-hosted</th><th>Basic</th><th>Standard</th><th>Advanced</th></tr></thead><tbody><tr><td><strong>CockroachDB MCP Server</strong><br /><br />The <InternalLink version="stable" path="cockroachdb-mcp-server">CockroachDB MCP Server</InternalLink> is a Model Context Protocol (MCP) server that lets your AI coding tools and AI agents explore live schemas, run queries, and write data on a CockroachDB cluster. The server runs as a local subprocess or a shared HTTP service and exposes a set of typed tools that agents invoke on your behalf. Once connected, you can interact with your cluster using natural language prompts to perform operations such as listing tables, executing <code>SELECT</code> statements, and inserting rows.</td><td class="icon-center">GA</td><td class="icon-center"><InlineImage alt="Green checkmark (Yes)" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Eo_circle_green_checkmark.svg" /></td><td class="icon-center"><InlineImage alt="Gray circle with horizontal white line (No)" src="https://upload.wikimedia.org/wikipedia/commons/b/b9/Eo_circle_grey_white_no-entry.svg" /></td><td class="icon-center"><InlineImage alt="Gray circle with horizontal white line (No)" src="https://upload.wikimedia.org/wikipedia/commons/b/b9/Eo_circle_grey_white_no-entry.svg" /></td><td class="icon-center"><InlineImage alt="Gray circle with horizontal white line (No)" src="https://upload.wikimedia.org/wikipedia/commons/b/b9/Eo_circle_grey_white_no-entry.svg" /></td></tr></tbody></table>

<a id="v26-3-0-features-that-require-upgrade-finalization" />

### Features that require upgrade finalization

This section summarizes the features that are not available until you <InternalLink version="stable" path="upgrade-cockroach-version#finalize-a-major-version-upgrade-manually">finalize the v26.3 upgrade</InternalLink>.

* The default `auto` value of the <InternalLink version="stable" path="cluster-settings">`kv.allocator.load_based_rebalancing`</InternalLink> cluster setting resolves differently after finalization. Before finalization, including while the cluster contains a mix of binary versions, `auto` resolves to `leases and replicas`: the <InternalLink version="stable" path="architecture/replication-layer#legacy-allocator">legacy allocator's</InternalLink> load-based rebalancer handles leases and replicas, using CPU as its load objective by default. After finalization, `auto` resolves to `multi-metric and count`: the <InternalLink version="stable" path="architecture/replication-layer#multi-metric-allocator">multi-metric allocator (MMA)</InternalLink> handles load-based lease and replica rebalancing, while the lease and replicate queues continue to balance lease and replica counts. As MMA converges under the changed placement heuristics, leases and replicas can move, and the cluster can settle at a different distribution of CPU load, including wider CPU load bands, because MMA makes different tradeoffs when placing leases and replicas. This behavior is expected and does not by itself indicate a performance problem. An explicitly configured value other than `auto` does not change at finalization.

* The `system.statement_statistics` metadata `JSONB` column no longer carries a copy of the statement query text, query summary, or database for rows newly flushed once the cluster has finalized v26.3. Consumers should source these values from `system.statements` (or via the `query`, `query_summary`, and `database` columns on the `crdb_internal.statement_statistics`, `crdb_internal.statement_persisted`, and `crdb_internal.statement_activity` views, which already join to it).

* Added a `REFERENCES` privilege for tables, matching PostgreSQL behavior. Foreign key creation now requires the `REFERENCES` privilege on the referenced (parent) table instead of `CREATE`. Existing users with `CREATE` on tables are automatically granted `REFERENCES` during the upgrade.

* Added support for PostgreSQL-compatible transaction-level advisory lock functions (`pg_advisory_xact_lock`, `pg_advisory_xact_lock_shared`, `pg_try_advisory_xact_lock`, and `pg_try_advisory_xact_lock_shared`, including `int4` two-argument overloads). Locks are tied to the SQL transaction and released on commit or rollback.

* Added the cluster setting `sql.procedures.plpgsql.late_binding.enabled` (defaults to `false`). When enabled, `PL/pgSQL` procedure bodies are not resolved at `CREATE PROCEDURE` time. References are instead resolved at `CALL` time, matching PostgreSQL `PL/pgSQL` semantics. `LANGUAGE` SQL procedures and functions are unaffected.

* Added the `crdb_internal.tsdb_query(name, start_time, end_time)` table function, which returns datapoints from the in-cluster time series database for a named metric over a specified time window. Calls require the `VIEWCLUSTERMETADATA` system privilege.

* Added support for an upper bound on execution latency when you request statement diagnostics bundles. Set `max_execution_latency` (optionally with `min_execution_latency`) to capture bundles only for statements whose execution latency falls within the specified range.

<a id="v26-3-0-breaking-changes" />

### Breaking changes

This section summarizes changes that can cause applications, scripts, or manual workflows to fail or behave differently than in previous releases. This includes [key cluster setting changes](#v26-3-0-key-cluster-setting-changes) and [deprecations](#v26-3-0-deprecations).

* User-defined views that reference `crdb_internal` virtual tables now enforce unsafe access checks. To restore the previous behavior, set the session variable `allow_unsafe_internals` or the cluster setting `sql.override.allow_unsafe_internals.enabled` to `true`.
* Changed privilege requirements for `RESTORE` statements for non-admin users. `RESTORE DATABASE` no longer accepts the deprecated `CREATEDB` role option, and requires the `RESTORE` system privilege. `RESTORE TABLE` no longer accepts the deprecated `CREATE` privilege, and requires the `RESTORE` privilege on the parent database.
* The `system.statement_statistics` metadata `JSONB` column no longer carries a copy of the statement query text, query summary, or database for rows newly flushed once the cluster has finalized 26.3. Consumers should source these values from `system.statements` (or via the `query`, `query_summary`, and `database` columns on the `crdb_internal.statement_statistics`, `crdb_internal.statement_persisted`, and `crdb_internal.statement_activity` views which already join to it).
* The `alter_type` event has been replaced by the operation-specific events `alter_type_add_value`, `alter_type_rename_value`, and `alter_type_drop_value`.
* DDL operations such as `CREATE TABLE` and `ALTER TABLE ... ADD COLUMN` now reject computed columns that use unsupported UDF patterns, including:
  * PL/pgSQL UDFs.
  * Multi-statement SQL UDFs.
  * UDFs with `OUT`/`INOUT` parameters.
  * Nested UDF calls.
  * UDF bodies with subqueries, CTEs, `FROM` clauses, or set-returning functions.
    Previously these were accepted but would fail during schema change backfill, leaving the table in a state where schema changes could not be performed.
* Fixed enforcement of`CREATE SCHEMA ... AUTHORIZATION` to require that the executing user is a member of the specified role, matching PostgreSQL behavior. Previously, any user with `CREATE` privilege on the database could create schemas owned by arbitrary roles.
* Added enforcement to return an error when casting an array containing `NaN`, `Infinity`, or zero elements to the `vector` type. This change matches PostgreSQL behavior.
* Index definitions now use only the schema-qualified table name (for example, `public.t`), instead of the previous behavior where `pg_get_indexdef()` and `pg_indexes.indexdef` included a database name prefix. This fix improves PostgreSQL tool compatibility.
* Changed the cluster setting `server.gc_assist.enabled` to default to `false`, disabling GC assist to help reduce tail latencies. This change should not affect CockroachDB workloads, but if needed you can re-enable GC assist setting `server.gc_assist.enabled` to `true`.
* Statement fingerprinting no longer distinguishes between implicit and explicit transactions. After you upgrade, you might temporarily see duplicate statement entries in **SQL Activity** until older, pre-upgrade statistics age out.
* Webhook CSV output now separates rows with newlines. Previously, multiple rows in a batch could be concatenated onto one line.
* Follow-the-workload rebalancing is now disabled by default as part of its deprecation. The `kv.allocator.load_based_lease_rebalancing.enabled` cluster setting is retired and hidden from `SHOW CLUSTER SETTINGS`, but can still be set to re-enable the feature if needed.
* Renamed the `exportrequest.delay.total` metric to `kv.bulk_low_pri_read.delay.total`. This metric now tracks throttling delay for all low-priority bulk read requests.
* Added a new cluster setting `sql.prepared_transactions.unsafe.enabled` (default: `false`) that controls whether `PREPARE TRANSACTION` statements are accepted. This setting is marked unsafe and requires the unsafe setting interlock to change. When disabled, attempting to prepare a transaction returns an error. `COMMIT PREPARED` and `ROLLBACK PREPARED` remain available regardless of this setting to allow cleanup of existing prepared transactions.
* Changed `ALTER TABLE ... RENAME TO`, `ALTER VIEW ... RENAME TO`, and `ALTER SEQUENCE ... RENAME TO` to require the `CREATE` privilege on the schema that contains the object (instead of `CREATE` on the database), matching PostgreSQL behavior. In the default configuration, renaming objects in the `public` schema is unaffected.
* `COPY ... FROM` in text format now correctly decodes `BYTES`/`BYTEA` input (including the standard `\x...` hex format), matching PostgreSQL. Previously, these values could be stored as literal text instead of being decoded. To restore the previous behavior for workloads that depend on it, set `copy_from_decode_bytes_enabled` to `off`.
* Changed execution insights to record only statements that have an insight (for example, slow or failed statements) within a flagged transaction, rather than recording every statement in the transaction. This results in the following changes:
  * `crdb_internal.{cluster,node}_stmt_execution_insights` no longer lists problem-free statements from flagged transactions.
  * The `query` column of `crdb_internal.{cluster,node}_txn_execution_insights` now includes only statements that generated an insight, and can be empty if the transaction was flagged due to an issue like a failure at `COMMIT` or high contention.
  * In the DB Console, the Transaction Insight Details page's Statement Executions tab now lists only statements that generated an insight. To view all statements for a transaction, including those hidden by this change, refer to the transaction in SQL activity.
* Changed `CREATE DOMAIN ... DEFAULT` and `ALTER DOMAIN ... SET DEFAULT` to sanitize and validate default expressions. These statements now reject defaults that reference other database objects.
* Changed `ALTER VIRTUAL CLUSTER ... RENAME TO` to disallow renaming a tenant while it is participating in a Physical Cluster Replication (PCR) stream.

<a id="v26-3-0-key-cluster-setting-changes" />

#### Key cluster setting changes

Review the following changes **before** upgrading. New default values will be used unless you have manually set a cluster setting value. To view the non-default settings on your cluster, run the SQL statement `SELECT * FROM system.settings`.

| Setting                         | Description                                                                                                                                                                                                                                                                      | Previous default | New default | Backported to versions |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ----------- | ---------------------- |
| `kv.range_merge.queue_interval` | Reduced the default value of the `kv.range_merge.queue_interval` cluster setting, which controls how long the merge queue waits between processing replicas. This helps the merge queue keep up with range creation and reduces range accumulation.                              | `5s`             | `200ms`     | None                   |
| `obs.ash.enabled`               | Changed the default value of the `obs.ash.enabled` cluster setting to `true`, enabling Active Session History sampling by default.                                                                                                                                               | `false`          | `true`      | None                   |
| `server.gc_assist.enabled`      | Changed the cluster setting `server.gc_assist.enabled` to default to `false`, disabling GC assist to help reduce tail latencies. This change should not affect CockroachDB workloads, but if needed you can re-enable GC assist by setting `server.gc_assist.enabled` to `true`. | `true`           | `false`     | None                   |

<a id="v26-3-0-deprecations" />

#### Deprecations

| Deprecated                                                    | Description                                                                                                                                                                                                                                                                 |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Follow-the-workload rebalancing                               | Follow-the-workload rebalancing is now disabled by default as part of its deprecation. The `kv.allocator.load_based_lease_rebalancing.enabled` cluster setting is retired and hidden from `SHOW CLUSTER SETTINGS`, but can still be set to re-enable the feature if needed. |
| `kv.bulk_io_write.concurrent_export_requests` cluster setting | The `kv.bulk_io_write.concurrent_export_requests` cluster setting has been renamed to `kv.bulk_low_pri_read.max_concurrent`, which now covers TTL scans as well as backup export requests.                                                                                  |
| `exportrequest.delay.total` metric                            | The `exportrequest.delay.total` metric has been replaced by `kv.bulk_low_pri_read.delay.total`. This metric now tracks throttling delay for all low-priority bulk read requests.                                                                                            |

<a id="v26-3-0-security-updates" />

### Security updates

* Added the `auth.min_password_length` metric that reports the configured minimum password length for the cluster. This metric can be used to monitor and alert on password length policy, ensuring compliance with organizational security requirements.
* Added the `auth.password_encryption.is_scram` metricto report whether password encryption is configured to use SCRAM-SHA-256 (1) or crdb-bcrypt (0). Use this metric to verify and alert on password hashing policy across the cluster, verifying security compliance requirements.
* Removed an overly restrictive TLS curve preference that limited FIPS mode to P-256. CockroachDB now uses Go's native FIPS curve selection, improving interoperability with clients that prefer other FIPS curves.
* Changed auditing so zone configuration changes (for example, `ALTER ... CONFIGURE ZONE`) are now logged to the `SENSITIVE_ACCESS` audit log channel for all users, not just admins. A new `zone_config_audit` event is emitted whenever a user sets or discards a zone configuration.
* Updated frontend dependencies to address known security vulnerabilities:
  * `lodash` updated to 4.18.1.
  * `immer` updated to 9.0.21.
  * `elliptic` updated to 6.6.1.
  * `bn.js` updated to 4.12.3/5.2.3.
* Fixed the declarative schema-changer component to further address the privilege-escalation vulnerability in constraint validation. Constraint, column `NOT NULL`, and `enum-value-removal` validation now execute under the schema-change issuer's privileges instead of with internal node-level privileges, so a UDF reached via a `CHECK` predicate, `RLS USING` clause, or virtual computed column is bounded by the issuer's own grants.
* Fixed a privilege-escalation vulnerability in Logical Data Replication. Writer-side internal-executor sessions previously ran with internal node-level privileges, so any user-supplied expression evaluated on the destination table (including the optional UDF configured by `CREATE LOGICAL REPLICATION STREAM ... WITH FUNCTION`, `CHECK` constraints, computed columns, RLS policy expressions, and trigger bodies) inherited that authority and could escalate any user with `REPLICATIONDEST` to admin via `crdb_internal.execute_internally`. LDR writer sessions now run with the stream creator's privileges, scoped to the destination tables; row-level-security policies on the destination are bypassed for the writer so replicated rows are applied rather than rejected by a policy the stream creator cannot satisfy.
* Fixed a dead-letter-queue component to further address the privilege-escalation vulnerability in constraint validation. An attacker with `CREATE` privileges on the destination database who pre-created the predictably-named DLQ table with extra columns, `CHECK` constraints, or triggers could otherwise have those evaluated under the LDR job owner's identity. The DLQ writer now refuses to use a DLQ table whose owner is not the node user, or a table the LDR job did not create.
* Fixed a privilege-escalation vulnerability in constraint validation. Previously, a SQL user with `CREATE TABLE` and `CREATE FUNCTION` privileges could escalate to admin by adding a `CHECK` constraint, an FK, an RLS policy, or a virtual computed column whose expression invoked a UDF that called `crdb_internal.execute_internally` with a privileged statement; the constraint and foreign-key validation queries ran with internal node-level privileges, which the UDF body inherited under `SECURITY INVOKER`. Validation now runs with the schema-change issuer's privileges.

<a id="v26-3-0-general-changes" />

### General changes

* Updated spatial libraries to use GEOS 3.14 instead of GEOS 3.13.

<a id="v26-3-0-sql-language-changes" />

### SQL language changes

* Updated CockroachDB to align with PostgreSQL version 18.0.0, including the following changes:
  * PostgreSQL version 18.0.0 is now reported in the `server_version` and `server_version_num` read-only configuration parameters.
  * Updated schemas for `pg_attribute`, `pg_auth_members`, `pg_class`, `pg_collation`, `pg_constraint`, `pg_database`, `pg_prepared_statements`, `pg_statistic_ext`, and `pg_type` with new and corrected columns
  * Fixed 14 pre-existing column type mismatches across `pg_am`, `pg_conversion`, `pg_enum`, `pg_extension`, `pg_operator`, `pg_range`, `pg_seclabel`, `pg_settings`, and `pg_stat_activity`.
  * Updated 35 unimplemented and stub table schemas to match PostgreSQL 18: `pg_group`, `pg_hba_file_rules`, `pg_indexes`, `pg_inherits`, `pg_language`, `pg_locks`, `pg_proc`, `pg_publication`, `pg_publication_rel`, `pg_publication_tables`, `pg_range`, `pg_replication_slots`, `pg_stat_activity`, `pg_stat_all_indexes`, `pg_stat_all_tables`, `pg_stat_database`, `pg_stat_database_conflicts`, `pg_stat_gssapi`, `pg_stat_progress_analyze`, `pg_stat_progress_vacuum`, `pg_stat_subscription`, `pg_stat_sys_indexes`, `pg_stat_sys_tables`, `pg_stat_user_indexes`, `pg_stat_user_tables`, `pg_stat_xact_all_tables`, `pg_stat_xact_sys_tables`, `pg_stat_xact_user_tables`, `pg_statio_user_sequences`, `pg_statistic_ext_data`, `pg_stats`, `pg_stats_ext`, `pg_subscription`, `pg_ts_parser`, and `pg_user_mapping`.
  * Added 15 new stub tables: `pg_aios`, `pg_backend_memory_contexts`, `pg_ident_file_mappings`, `pg_parameter_acl`, `pg_publication_namespace`, `pg_shmem_allocations_numa`, `pg_stat_checkpointer`, `pg_stat_io`, `pg_stat_progress_copy`, `pg_stat_recovery_prefetch`, `pg_stat_replication_slots`, `pg_stat_subscription_stats`, `pg_stat_wal`, `pg_stats_ext_exprs`, and `pg_wait_events`.
* Added the `sql.udf.count` metric, which tracks the number of SQL statements that invoke a user-defined function (UDF).
* Added the `information_schema.crdb_statement_statistics` and `information_schema.crdb_transaction_statistics` views to expose persisted SQL statement and transaction statistics with a stable schema. Data can lag by up to one SQL stats flush interval (10 minutes by default). For real-time statistics, continue using `crdb_internal.cluster_statement_statistics`.
* Added `information_schema.crdb_jobs_with_progress`, a stable view exposing per-job metadata together with the current progress fraction, resolved HLC, status message, and last-updated timestamp. The `resolved` column is the raw HLC; apply `hlc_to_timestamp` at the call site if a wall-clock value is needed.
* Added `information_schema.crdb_job_messages(job_id)`, a set-returning function that returns the message history for one job (`recorded`, `kind`, `message`). The argument is mandatory; an unknown or invisible `job_id` returns zero rows.
* Added `information_schema.crdb_jobs`, a stable view exposing per-job metadata from the jobs system intended for programmatic use. Unlike `SHOW JOBS`, the `description` column is returned in full and the `error` column distinguishes `NULL` (no error) from the empty string. The lifecycle column is named `state`.
* Added `information_schema.crdb_job_progress_history(job_id)`, a set-returning function that returns the full progress trajectory of a job (`recorded`, `progress_fraction`, `resolved`). `resolved` is the raw HLC decimal; apply `hlc_to_timestamp` at the call site for a wall-clock value. The argument is mandatory; an unknown or invisible `job_id` returns zero rows.
* Added a new `VIEWEVENTLOG` system privilege that grants read-only access to the event log. Non-admin users can be granted event log visibility via `GRANT SYSTEM VIEWEVENTLOG TO <user>` without needing `VIEWCLUSTERMETADATA` or the `admin` role.
* Added support for `ALTER DOMAIN ... SET DEFAULT` and `ALTER DOMAIN ... DROP DEFAULT`.
* Added support for the `bit_xor` aggregate function, which computes the bitwise XOR of all non-null input values (or `NULL` if there are none) for integer and bit-string inputs, matching PostgreSQL.
* Added the cluster setting `sql.status.active_query_text.max_bytes`, which controls the maximum length of SQL text shown in `SHOW CLUSTER QUERIES`, `SHOW CLUSTER SESSIONS`, and the corresponding `crdb_internal` virtual tables. Statements longer than this limit are truncated with a trailing ellipsis.
* Added support for `CREATE DOMAIN` and `DROP DOMAIN` syntax, to define named types based on existing types with optional constraints.
* Added `ALTER DATABASE ... ALTER SUPER REGION ... SURVIVE {ZONE|REGION} FAILURE` syntax, allowing individual super regions to have their own survival goal independent of the database default.
* Added support for configuring zone settings at the super region level in multi-region databases using `ALTER DATABASE ... ALTER LOCALITY SUPER REGION ... CONFIGURE ZONE`. This allows setting zone configuration properties (for example, `num_voters` and `num_replicas`) that apply to all tables and partitions whose affinity region belongs to the specified super region.
* Added support for the `TRUNCATE` table privilege via `GRANT` and `REVOKE`, for parity with PostgreSQL. For backwards compatibility, `TRUNCATE` operations are allowed for users that have either `TRUNCATE` or `DROP` privileges.
* Added a new cluster setting, `sql.schema.auto_unlock.enabled`, that controls whether DDL operations automatically unlock `schema_locked` tables. When set to `false`, DDL on schema-locked tables is blocked unless the user manually unlocks the table first. This allows customers using LDR to enforce `schema_locked` as a hard lock that prevents user-initiated DDL. The default is `true`, preserving existing behavior.
* Added support for the `SHOW RANGES .. WITH ZONE` option which adds a `zone_config` column of type `JSONB` containing the fully resolved zone configuration for each range (with inheritance applied).
* Added a `SKIP FORIEGN KEYS` option for `CREATE LOGICALLY REPLICATED TABLE`, which creates the destination table without foreign key constraints.
* Added support for `ALTER DOMAIN ... RENAME TO` statements.
* Added support for an upper bound on execution latency when you request statement diagnostics bundles. Set `max_execution_latency` (optionally with `min_execution_latency`) to capture bundles only for statements whose execution latency falls within the specified range.
* Added support `DROP PROVISIONED ROLES` statements to bulk-drop provisioned roles with optional filter criteria, skipping users that own objects or have other dependencies.
* Added `WITH` and `LIMIT` filters to `SHOW USERS` to support provisioned users. Use `SOURCE` to filter by provisioning source and `LAST LOGIN BEFORE` to return users whose last login was before a given timestamp (excluding users who have never logged in). The original `SHOW USERS` behavior without clauses is unchanged.
* `SHOW ROLES` now supports the same `WITH SOURCE`, `LAST LOGIN BEFORE`, and `LIMIT` clauses as `SHOW USERS`.
* Added support for subscript access on values of type `name` using 0-based indexing (for example, `('hello'::name)[0]` returns `h`). Out-of-bounds subscripts return `NULL`, matching PostgreSQL behavior.
* Added the built-in function `pg_get_statisticsobjdef()`, which returns the `CREATE STATISTICS` statement for an extended statistics object.
* Added pretty-print style to statement bundles with `CREATE FUNCTION` and `CREATE PROCEDURE`.
* Added support to `SHOW TRACE FOR SESSION` for span tags such as `_verbose`, `_dropped_logs`, and `_dropped_children`. Tags use the `=== operation:<name> <tags>` format for span start messages to identify when trace output is truncated.
* Added a `pg_dump_compatibility` session variable that improves compatibility with the `pg_dump` PostgreSQL tool. Set it to `postgres` to make `pg_catalog` report OIDs that match the hardcoded ones expected by `pg_dump`, hide CockroachDB-internal objects, and make other fixups that allow `pg_dump` output to run on non-CockroachDB servers. Set it to `cockroachdb` for the same `pg_catalog` fixes while keeping CockroachDB-specific syntax. Like other session variables, `pg_dump_compatibility` can be set in the connection string.
* Added additional execution statistics to `EXPLAIN ANALYZE` for vector index operations including contention and wait times, MVCC scan statistics, and tenant RU consumption.
* Added KV statistics to `EXPLAIN ANALYZE` for vector search and vector mutation search operations, including gRPC calls, bytes read, pairs read, KV time, and KV CPU time.
* Added support for PostgreSQL-compatible transaction-level advisory lock functions (`pg_advisory_xact_lock`, `pg_advisory_xact_lock_shared`, `pg_try_advisory_xact_lock`, and `pg_try_advisory_xact_lock_shared`, including `int4` two-argument overloads). Locks are tied to the SQL transaction and released on commit or rollback.
* Added `query`, `query_summary`, and `database` columns to `crdb_internal.cluster_statement_statistics`, `crdb_internal.statement_statistics`, `crdb_internal.statement_statistics_persisted`, and `crdb_internal.statement_activity`. These columns expose the statement text, summary, and originating database directly, without requiring callers to parse the `metadata` `JSONB` column.
* Added support for the `ST_3DDistance` and `ST_3DDWithin` geospatial functions, which compute minimum distance and within-distance checks using 3D Euclidean distance. These functions consider the Z coordinate of geometries, unlike their 2D counterparts `ST_Distance` and `ST_DWithin`.
* `EXPLAIN` now recommends vector indexes for queries that use vector distance operators (`<->`, `<=>`, `<#>`) with `ORDER BY ... LIMIT`, including support for equality prefix columns and all three distance metrics (L2, cosine, inner product).
* Added a new cluster setting `sql.stats.table_statistics_cache.capacity` that controls the maximum number of tables whose statistics are retained in the in-memory LRU cache (default: `256`).
* Added support for the `SEQUENCE NAME <name>` clause on `ALTER TABLE ... ADD GENERATED ... AS IDENTITY` and `ALTER TABLE ... ADD COLUMN ... GENERATED AS IDENTITY`. The clause names the backing sequence explicitly instead of using the auto-generated `<table>_<column>_seq` pattern.
* Added the SQL CPU timeline to `EXPLAIN ANALYZE` for all queries, including mutations and queries run through the row-by-row execution engine. Previously, this line was only shown for non-mutation queries that ran through the vectorized execution engine.
* `CREATE SCHEMA`, `DROP SCHEMA`, `CREATE ROLE`, and `DROP ROLE` can now be used in PL/pgSQL stored procedure bodies.
* Added support for `ALTER DOMAIN ... OWNER TO`.
* Added an `options` argument to `crdb_internal.tsdb_query()` to control TSDB-side downsampling, derivatives, source filtering, and cross-source aggregation. The supported JSONB keys are documented in the function description (use `\\df+ crdb_internal.tsdb_query`).
* Added the cluster settings `sql.crdb_internal.tsdb_query.max_time_range` and `sql.crdb_internal.tsdb_query.max_rows` to limit the amount of work a single `crdb_internal.tsdb_query()` call can perform. Queries that exceed either limit are rejected with an error that identifies which setting to adjust.
* Added the `crdb_internal.tsdb_query(name, start_time, end_time)` table function, which returns datapoints from the in-cluster time series database for a named metric over a specified time window. Calls require the `VIEWCLUSTERMETADATA` system privilege.
* Added the `pg_database_size` PostgreSQL-compatible built-in function. It returns an approximate on-disk size sourced from a periodically refreshed cache.
* Added the `pg_relation_size`, `pg_table_size`, and `pg_total_relation_size` PostgreSQL-compatible built-in functions for table relations. Sizes come from a periodically refreshed cache and may lag the true value by minutes. Index OIDs and `pg_indexes_size` are not yet supported; they will be added in a follow-up.
* Added the `pg_size_pretty` and `pg_size_bytes` PostgreSQL-compatible built-in functions.
* Added support for `COMMENT ON VIEW` and `COMMENT ON SEQUENCE` statements. Comments set on views and sequences are visible via `pg_catalog.pg_description` and the `obj_description()` built-in function.
* Added support for `COMMENT ON FUNCTION`, `COMMENT ON PROCEDURE`, and `COMMENT ON ROUTINE` statements on user-defined functions and stored procedures. Comments are visible via `pg_catalog.pg_description` and the `obj_description(oid, 'pg_proc')` built-in function. The argument list is required when the routine name is overloaded and may be omitted otherwise. The `COMMENT ON ROUTINE` statement accepts either a function or a procedure, matching PostgreSQL. Comments are not supported on built-in functions.
* Added support for `LIST` columns to `IMPORT INTO ... PARQUET`. A `LIST` is decoded into a target `ARRAY` column (with an element type matching the Parquet element's physical or logical type) or a target `JSONB` column (serialized as a JSON array). When a `LIST` element is an unannotated `BYTE_ARRAY`, the bytes are passed through to the target as a string with no UTF-8 validation. For binary data, target an `ARRAY<BYTES>` column to preserve the original bytes losslessly. Nested `LIST`s and `MAP` columns are unsupported and rejected at file-open time.
* Added support for SQL-standard inline body syntax for SQL routines. `CREATE FUNCTION` and `CREATE PROCEDURE` with `LANGUAGE SQL` may now use either `BEGIN ATOMIC ... END` (with one or more body statements) or a bare `RETURN expr` in place of the dollar-quoted `AS $$ ... $$` form. Routines created with the inline form display as the dollar-quoted form in `SHOW CREATE` output.
* `CREATE TABLE` and `ALTER TABLE ... ADD COLUMN` now support `GENERATED AS IDENTITY (SEQUENCE NAME {name})`. This clause allows you to explicitly name the backing sequence instead of using the auto-generated `<table>_<column>_seq` sequence name.
* Added PostgreSQL-compatible built-in functions `factorial`, `gcd`, `lcm`, `scale`, `min_scale`, `trim_scale`, `log10`, `erf`, `erfc`, and `random_normal`.
* Added a 3-argument overload of `ST_DistanceSpheroid` that accepts a textual `SPHEROID` definition (e.g., `'SPHEROID["GRS_1980",6378137,298.257222101]'`) as the third argument, matching the PostGIS signature. The supplied spheroid is used for the geodesic distance computation in place of the one derived from the geographies' SRID.
* Added a `REFERENCES` privilege for tables, matching PostgreSQL behavior. Foreign key creation now requires the `REFERENCES` privilege on the referenced (parent) table instead of `CREATE`. The child (origin) table still requires `CREATE`. Existing users with `CREATE` on tables are automatically granted `REFERENCES` during upgrade. The `REFERENCES` privilege can be granted and revoked independently via `GRANT`/`REVOKE REFERENCES ON <table>`. Note that restoring a pre-26.3 backup onto a 26.3+ cluster does not automatically grant `REFERENCES` to users who had `CREATE`; an explicit `GRANT REFERENCES` is needed to create foreign keys on restored tables.
* Added support for creating foreign keys when the referenced table has a unique constraint on a subset of the referenced columns. Creation of new subset-unique foreign keys can be disabled by setting the `sql.subset_unique_fks.enabled` cluster setting to `false`.
* Added partial support for `ALTER DOMAIN ... SET NOT NULL` and `ALTER DOMAIN ... DROP NOT NULL`. The constraint is currently treated as NOT VALID which limits checks to newly inserted or updated rows.
* Stored procedures now support `GRANT`, `REVOKE`, and `ALTER DEFAULT PRIVILEGES`. These statements remain unsupported inside user-defined functions and `DO` blocks.
* Improved `CREATE PROCEDURE` validation for `PL/pgSQL` procedure bodies that contain DDL.
  * When late binding is disabled, procedure bodies containing DDL are now rejected with an error message referencing the `sql.procedures.plpgsql.late_binding.enabled` cluster setting.
  * When late binding is enabled, `CREATE SCHEMA`, `DROP SCHEMA`, `CREATE ROLE`, and `DROP ROLE` are now allowed in `PL/pgSQL` procedure bodies.
* Improved out-of-the-box support for dump/restore clients. Connecting a tool to CockroachDB with an `application_name` of `pg_dump`, `pg_restore`, or `pg_dumpall` now automatically sets the `pg_dump_compatibility` session setting to "cockroachdb" (emitting a `NOTICE`), unless the setting is provided explicitly in the connection string.
* Added the session setting `optimizer_inline_placeholder_equalities`, defaults to `true`. When set to `false`, the optimizer does not propagate placeholder equality constraints into correlated subqueries.
* Added `sql.routine.<type>.started.count` and `sql.routine.<type>.count` SQL statement metrics for DDL and DCL executed inside stored procedure bodies.
* Added the cluster setting `sql.procedures.plpgsql.late_binding.enabled` (defaults to `false`). When enabled, `PL/pgSQL` procedure bodies are not resolved at `CREATE PROCEDURE` time. References are instead resolved at `CALL` time, matching PostgreSQL `PL/pgSQL` semantics. `LANGUAGE` SQL procedures and functions are unaffected.
* Added support for the `pg_locks` table, which only supports the monitoring of advisory locks.
* A new cluster setting `sql.log.failed_query.enabled` causes every SQL statement that ends in an error to be logged on the `SQL_EXEC` log channel as a `failed_query` event, including the statement text, SQLSTATE, and error text. A companion setting `sql.log.failed_query.internal_queries.enabled` does the same for internally executed statements. Both default to off. Operators can use this to derive custom metrics for specific error classes without enabling `sql.log.all_statements.enabled`.
* Added support for the `ST_3DMaxDistance`, `ST_3DDFullyWithin`, `ST_3DIntersects`, `ST_3DShortestLine`, `ST_3DLongestLine`, `ST_3DClosestPoint`, and `ST_3DPerimeter` geospatial functions.
* CockroachDB now recognizes the fixed-offset timezone abbreviations defined by PostgreSQL's `pg_timezone_abbrevs` view (such as `EST`, `PST`, `EAT`, `CET`) when parsing `timestamptz` literals. Previously these abbreviations returned an "unimplemented" error. The abbreviations are also now exposed via the `pg_catalog.pg_timezone_abbrevs` virtual table.
* PL/pgSQL routines can now access fields of composite-typed variables without surrounding the variable name in parentheses (for example `v.x` in addition to the previously required `(v).x`), matching PostgreSQL behavior. When a PL/pgSQL variable shadows a column with the same name in a SQL statement inside the routine, the reference is now rejected as ambiguous instead of being silently resolved.
* Two new session variables, `distsql_plan_locality_filter` and `distsql_plan_locality_filter_strict`, allow restricting DistSQL physical planning of a session's queries to SQL instances whose locality matches a user-supplied filter. These variables mirror the `EXECUTION LOCALITY` option already available for `BACKUP`, `RESTORE`, and `CHANGEFEED` jobs.
* Added the `pg_get_function_sqlbody` built-in function for compatibility with PostgreSQL 14+. The function returns `NULL` for all functions today, matching PostgreSQL's behavior for functions defined with the `AS $$..$$` syntax.
* `ST_3DDistance` and `ST_3DDWithin` now fall back to 2D distance when either input lacks a Z dimension, matching PostGIS. Previously the missing Z was treated as 0.
* CockroachDB now supports the `WITH NO DATA` option for `CREATE TABLE ... AS`. When specified, the `AS` query is planned to derive the new table's column names and types, but is not executed, so the table is created empty. This matches PostgreSQL: the resulting table is an ordinary, immediately-usable table (it can be selected from and inserted into right away), unlike a materialized view created with `WITH NO DATA`, which must be refreshed before it can be queried. `WITH DATA` remains the default and populates the table from the query.
* `EXPLAIN` and `EXPLAIN ANALYZE` now display a `table stats mode` field (`canary` or `stable`) when the `sql.stats.canary_fraction` cluster setting is greater than 0, indicating which table statistics were used for query planning. Scan nodes for tables with active canary stats also show the configured canary window duration.
* Exposed the following settings for canary table statistics:
  * Cluster setting `sql.stats.canary_fraction`: probability that table statistics will use canary mode (i.e., always use the freshest stats) instead of stable mode (i.e., use the second-freshest stats) for query planning \[0.0-1.0].
  * Session variable `canary_stats_mode`: When `sql.stats.canary_fraction` is greater than `0`, controls which table statistics are used for query planning on the current session: `on` always uses the newest (canary) stats immediately when they are collected, `off` delays using new stats until they outlive the canary window, and `auto` selects probabilistically based on the canary fraction. Has no effect when `sql.stats.canary_fraction` is `0`.
* `EXPLAIN (VERBOSE)` and `EXPLAIN ANALYZE (VERBOSE)` now include a detailed tree of statement hints showing each hint's type, configuration (donor SQL or variable name/value), and skip reason if applicable.
* `EXPLAIN` and `EXPLAIN ANALYZE` now report statement hints as applied or skipped. Hints that fail at runtime (for example, referencing a missing index or an invalid session variable) appear as skipped. `EXPLAIN (VERBOSE)` and `EXPLAIN ANALYZE (VERBOSE)` also include per-hint details and the skip reason when applicable.
* Aggregation function `ST_AsMVT` can now also be used as a window function.
* `CREATE CHANGEFEED FOR DATABASE` now returns an error stating that the feature is not implemented.
* Changed the default value of the session variable `optimizer_span_limit` to `131072`. This bounds the number of spans the optimizer will allow in constrained index scans generated during query optimization. Queries that would exceed this limit will use fewer looser spans with remaining filters instead. Set to `0` to disable the limit.
* `REPLACE FUNCTION`/`PROCEDURE` in the legacy schema changer now strictly requires function ownership, matching the declarative schema changer and PostgreSQL behavior.
* Reduced the necessary user privileges for `SHOW SCHEDULES`, only requiring the `VIEWJOB` system privilege. Non-admin users can be granted schedule visibility via `GRANT SYSTEM VIEWJOB TO <user>` without needing direct `SELECT` on system tables.
* Changed statement bundles to pretty-print `DO` statements.
* Setting `skip_unique_checks = true` on an index now emits a notice warning that unique constraint enforcement is bypassed, with a pointer to the `INSPECT` documentation.
* Changed the `create_statement` column returned by `SHOW CREATE FUNCTION` and `SHOW CREATE PROCEDURE` to be pretty-printed.
* `pg_catalog` no longer shows the synthetic primary key constraint or its backing index for materialized views. This matches PostgreSQL, which never attaches a constraint or index to a materialized view implicitly.
* Changed statement hints so creating a hint that conflicts with an existing hint now emits a `NOTICE` indicating that older hints will be skipped. Use `SHOW STATEMENT HINTS` to identify stale hints.
* Renamed the `canary_stats_mode` session variable values from `"off"`/`"on"` to `"force_stable"`/`"force_canary"`. These modes now work independently of the `sql.stats.canary_fraction` cluster setting, allowing per-session opt-in without cluster-wide enrollment.
* Conflicting names in `ALTER TYPE ... RENAME VALUE` now error consistently with PostgreSQL.
* `ALTER TYPE ... SET (property = value)` now returns a clear unimplemented error instead of a syntax error.
* Removed the `implicit_txn` field from statement statistics. The `implicit_txn` column is no longer present in `crdb_internal.node_statement_statistics`, and statement statistics stored in `system.statement_statistics` no longer include an `implicitTxn` metadata key.
* The `custom_plans` and `generic_plans` columns in `pg_prepared_statements` now report the number of times a custom or generic plan was used for each prepared statement, matching PostgreSQL behavior.
* CockroachDB now sends `default_transaction_read_only`, `in_hot_standby`, `search_path`, and `scram_iterations` as `ParameterStatus` messages during connection startup, matching PostgreSQL 18 behavior.
* The `pg_catalog.pg_proc` columns `procost`, `prorows`, `prosupport`, and `proparallel` now return PostgreSQL-compatible default values instead of `NULL`. Built-ins report `procost=1`, user-defined functions report `procost=100`, `prorows` is `1000` for set-returning routines and `0` otherwise, `prosupport` is `-`, and `proparallel` is `u` (unsafe) for all routines because CockroachDB does not track parallel safety.
* `pg_catalog.pg_constraint` now exposes `NOT NULL` constraints as named entries, matching PostgreSQL behavior for non-nullable columns. These entries use `contype='n'`, set `conkey` to an array containing the column's `attnum`, and set `conname` to a name like `{table}_{column}_not_null`.
* `GENERATED ALWAYS AS (<expr>)` and `AS (<expr>)` column definitions no longer require a trailing `STORED` or `VIRTUAL` keyword. When neither is specified, the column defaults to `VIRTUAL`, matching PostgreSQL.
* Updated `pg_relation_size` and `pg_table_size` to return the size of the primary index only, matching PostgreSQL's "heap only" semantics. The previous (over-counting) behavior is preserved by `pg_total_relation_size`. `pg_relation_size` also accepts an index OID, returning that index's cached size. The new `pg_indexes_size()` built-in function returns the sum of the relation's secondary index sizes.
* The `ALTER DOMAIN ... SET SCHEMA` statement is now supported.
* DDL executed inside a stored procedure now honors the `SERIALIZABLE` isolation requirement that top-level DDL has always had: when a procedure containing DDL is called under `READ COMMITTED` or `REPEATABLE READ` in an implicit transaction, the transaction is automatically upgraded to `SERIALIZABLE` for the call; otherwise the call is rejected with a clear error.
* Unimplemented PL/pgSQL syntax errors now include a link to the GitHub issue tracking the missing feature.
* Fixed the error messages for `GRANT ROLE` and `REVOKE ROLE` inside functions and procedures to correctly identify the statement as `GRANT ROLE` / `REVOKE ROLE` instead of the generic `GRANT` / `REVOKE`.
* Added support for `ALTER TRIGGER ... ON ... RENAME TO` to rename a trigger on a table. Renaming a trigger requires `OWNERSHIP` on the table. CockroachDB also supports `ALTER TRIGGER IF EXISTS` (an extension to PostgreSQL), which performs no action if the table or trigger does not exist.
* Added support for `ALTER DOMAIN ... ADD CONSTRAINT`. Constraints added this way are created as `NOT VALID` (they are not checked against existing rows).
* Improved `ALTER TABLE ... ALTER COLUMN TYPE` so it can succeed even when the column is referenced by dependent user-defined functions or stored procedures, as long as the type change is compatible with the routine bodies. Incompatible changes now return an error that identifies the routine that would become invalid.
* Improved PostgreSQL compatibility for sequences:
  * Added the built-in function `pg_get_sequence_data()` for compatibility with `pg_dump`, which uses it to read sequence data during dumps.
  * Fixed `SELECT last_value, is_called FROM <sequence>` on a sequence that has never been used to return the sequence start value with `is_called = false` (matching PostgreSQL behavior).
* `COPY FROM` now accepts values for `GENERATED ALWAYS AS IDENTITY` columns and writes them as supplied, matching PostgreSQL behavior. Previously, such columns could not appear in a `COPY` column list.
* Changed `DROP TRIGGER` to allow users with the `DROP` privilege on a table (in addition to the table owner and admins) to drop triggers on that table.
* The `CREATE DOMAIN ... CHECK` statements now sanitize and validate check expressions. References to descriptors are rejected.
* The `information_schema.crdb_node_active_session_history` and `information_schema.crdb_cluster_active_session_history` views now include the `query`, `query_summary`, and `database` columns for statement samples, which makes Active Session History easier to interpret without manually joining to statement statistics.
* Changed `SHOW RANGES WITH ZONE` by renaming the `zone_config_conformant` column to `zone_config_needs_split`. The boolean meaning is now inverted: `true` indicates the range crosses a zone configuration boundary and should be split.
* Added per-execution enrichment columns to the virtual tables `crdb_internal.node_active_session_history` and `crdb_internal.cluster_active_session_history`, and to the table `system.active_session_history`, when ASH enrichment is enabled with the cluster setting `obs.ash.enrichment.enabled`. The new columns are `user`, `plan_gist`, `canary_stats`, `txn_id`, `session_id`.
* `ALTER DOMAIN ... ADD CONSTRAINT` and `ALTER DOMAIN ... NOT NULL` now validate new constraints against existing data before applying them.
* Added support for the PostgreSQL-compatible `OVERRIDING SYSTEM VALUE` and `OVERRIDING USER VALUE` clauses to `INSERT` for identity columns:
  * `OVERRIDING SYSTEM VALUE` allows inserting an explicit value into a `GENERATED ALWAYS AS IDENTITY` column.
  * `OVERRIDING USER VALUE` ignores explicit identity-column values and uses the sequence-generated value instead.
* Added the per-table storage parameter `sql_stats_forecasts_min_goodness_of_fit`, which lets you override the cluster setting `sql.stats.forecasts.min_goodness_of_fit` for individual tables to better support statistics forecasting on tables with irregular growth patterns.
* Added support for `ALTER DOMAIN ... RENAME CONSTRAINT`.
* Added the view `information_schema.crdb_persisted_active_session_history`, which exposes durable, cluster-wide Active Session History (ASH) samples stored in `system.active_session_history`. The view returns the same columns as `crdb_node_active_session_history` and `crdb_cluster_active_session_history`, including the sampled statement's `query`, `query_summary`, and `database`.
* Added Active Session History (ASH) per-execution enrichment columns to the `information_schema` views `crdb_node_active_session_history`, `crdb_cluster_active_session_history`, and `crdb_persisted_active_session_history`: `user`, `plan_gist`, `canary_stats`, `txn_id`, and `session_id`.
* Added support for `ALTER DOMAIN ... DROP CONSTRAINT`.
* The `crdb_delete_statement_hints` and `crdb_enable_statement_hints` built-ins now accept an optional database argument along with a hint row ID, allowing users with database-scoped `STATEMENTHINTS` to manage individual statement hints.
* Added a new `STATEMENTHINTS` privilege to control access to the statement-hints built-ins (e.g., `crdb_rewrite_inline_hints`, `crdb_delete_statement_hints`, `crdb_enable_statement_hints`, `crdb_set_session_variable_hint`, `crdb_pin_plan_gist`, `crdb_pin_plan_gram`) without requiring `REPAIRCLUSTER`. This privilege can be granted at the system level or per database; `REPAIRCLUSTER` continues to grant access for backward compatibility.
* CREATE / ALTER / DROP / SHOW RESOURCE GROUP return an unimplemented error at plan time by default. Setting sql.experimental\_resource\_groups.enabled = true bypasses this and re-enables the previous behavior for internal testing.
* Added the `optimizer_use_routine_decorrelation_fix` session setting. When enabled, the optimizer delays certain query rewrites for user-defined functions (UDFs) and stored procedures until the routine is inlined into the calling query or executed, which can help prevent filters that reference routine parameters from being moved in ways that block constrained scans (for example, causing a lookup plan to degrade to a full scan).
* Foreign key referential actions (for example, `ON DELETE CASCADE` and `ON UPDATE CASCADE`) now run with the privileges of the referencing table owner (including any triggered triggers and check constraints), rather than the user performing the mutation. This matches PostgreSQL behavior and can reduce the privileges required for users who modify referenced tables. The row-level TTL job now also runs as the table owner instead of the node user.
* Row-level TTL jobs now run their `SELECT` and `DELETE` operations as the TTL table's owner instead of an internal superuser. As a result, any user-defined code invoked by TTL deletes (for example, `DELETE` triggers) runs with the table owner's privileges and sees `current_user` as the table owner. Row-level TTL still deletes expired rows even when `FORCE ROW LEVEL SECURITY` would hide them from the owner, or when the owner lacks `EXECUTE` privileges on referenced functions.
* Added a new session variable `optimizer_span_limit` that bounds the number of spans the optimizer will allow in a single constrained index scan. If a single `IN` set has more items than this limit, that `IN` set will not be used to build a constrained index scan. If the cross product of two or more `IN` sets would produce more spans than this limit for a composite index, then only a prefix of the `IN` sets will be used to produce spans.

  For example, for the following table and query, only the predicates on columns `a` and `b` will be used to construct the constrained scan of `abc_idx`, because including the predicate on column `c` would produce more spans than `optimizer_span_limit`:

  ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  CREATE TABLE abc (a INT, b INT, c INT, INDEX abc_idx (a, b, c));
  SET optimizer_span_limit = 10;
  SELECT * FROM abc WHERE a IN (1, 3, 5) AND b IN (2, 4, 6) AND c IN (7, 9, 11);
  ```
* Added a `csv_header` option to `CREATE CHANGEFEED` that adds a header row to `csv` format changefeeds to webhook and cloud storage sinks. When `csv_header` is enabled, CockroachDB includes a header row of column names at the start of each webhook request body and at the beginning of each generated CSV file.
* `CREATE CHANGEFEED` now supports the `create_kafka_topics` option to control Kafka topic creation: `broker_auto` (default) relies on the Kafka cluster to auto-create topics, `explicit` creates topics using the Kafka Admin API, and `off` disables topic creation.
* Changed transactional logical data replication to require the `CURSOR` parameter.

<a id="v26-3-0-operational-changes" />

### Operational changes

* Changed the default value of the `obs.ash.enabled` cluster setting to `true`, enabling Active Session History sampling by default.
* Added the `obs.ash.enabled`, `obs.ash.sample_interval`, `obs.ash.buffer_size`, `obs.ash.log_interval`, `obs.ash.log_top_n`, and `obs.ash.response_limit` cluster settings. These settings control Active Session History (ASH) sampling frequency, buffer size, logging intervals, and query limits.
* Statement diagnostics requests with `sampling_probability` and `expires_at` now collect up to 10 bundles (configurable via `sql.stmt_diagnostics.max_bundles_per_request`) instead of a single bundle. Set the cluster setting to `1` to restore single-bundle behavior.
* Added two new metrics, `auth.cert.san.conn.total` and `auth.cert.san.conn.success`, to track SAN-based certificate authentication attempts and successes.
* Added the metrics `storage.wal.failover.secondary.disk.capacity` and `storage.wal.failover.secondary.disk.available` to report disk space utilization of the secondary WAL volume when WAL failover is configured.
* A new cluster setting, `server.gc_assist.enabled`, allows operators to dynamically disable GC assist in CockroachDB's forked Go runtime. By default, it follows the `GODEBUG=gcnoassist` flag. A new metric, `sys.gc.assist.enabled`, reports the current state (`1` = enabled, `0` = disabled).
* Added support for a visibility level filter on the Prometheus scrape endpoints `/_status/vars` and `/metrics`. Use the `?visibility=` query parameter (`all`, `support`, or `essential`) or the cluster setting `obs.metrics_scrape.default_visibility` to control which metrics are exported. Default value is `all` to support existing behavior.
* Added the `liveness.uncached_scans` metric, which counts direct KV scans of the node liveness range.
* Added throttling for low-priority bulk read operations, such as TTL `SCAN` and backup `EXPORT` operations. Configure throttling with the `kv.bulk_low_pri_read.max_rate` and `kv.bulk_low_pri_read.max_concurrent` cluster settings.
* Added certificate lifecycle metrics to improve observability and alerting for certificate health:
  * `security.certificate.last_rotation` (exported as `security_certificate_last_rotation` in Prometheus) reports the Unix timestamp, in seconds, of the most recent certificate rotation. Reports `0` if the process has not rotated the certificate since startup.
  * `security.certificate.expiry_days` (exported as `security_certificate_expiry_days` in Prometheus) reports the number of days remaining until each certificate expires. Reports `0` if the certificate is expired, missing, or cannot be loaded.
* Physical cluster replication now returns clearer errors if you attempt to cut over before the initial scan replicates any data. Cutting over to `LATEST` during the initial scan now returns an explicit error instead of using the replication start time. Cutting over to an explicit timestamp now validates that the timestamp is not earlier than the replication start time.
* The cluster settings `storage.sstable.compression_algorithm_backup_storage` and `storage.sstable.compression_algorithm_backup_transport` no longer appear in `SHOW ALL CLUSTER SETTINGS`. These settings can still be viewed and set explicitly by name.
* Added additional statement statistics to the `sql.stats.discarded.current` to report statement drops due to memory pressure and fingerprint-tracking limits on the SQL stats ingester.
* Added three new admission control metrics for monitoring disk bandwidth token usage: `admission.granter.disk_write_byte_tokens_used.regular.kv`, `admission.granter.disk_write_byte_tokens_used.elastic.kv`, and `admission.granter.disk_write_byte_tokens_used.snapshot.kv`. The existing `admission.granter.disk_write_byte_tokens_exhausted_duration.kv` metric is now marked as essential and will appear on the **Overload** dashboard.
* Four new gauges `mma.overloaded_store.{lease_grace,short_dur,medium_dur,long_dur}.blocked` report overloaded stores that the multi-metric allocator (MMA) deferred because they already had too much pending work. Per duration bucket, success + failure + blocked equals the count of overloaded stores observed. A persistently non-zero value on the `long_dur.blocked` gauge indicates an overloaded store that is repeatedly being deferred and may not be receiving relief.
* The `changefeed.checkpoint_lag` metric is now reported per job and fixed to report correctly when changefeed jobs are paused.
* Active Session History (ASH) now includes a `COMMIT` workload type that attributes commit-deferred work for an explicit transaction with the transaction fingerprint as the `workload_id`.
* Added the cluster setting `sql.insights.cooldown.duration` (default `5m`), which suppresses repeated execution insights for the same transaction fingerprint and problem profile within a configurable window. This helps prevent noisy transactions from crowding out the bounded insights store; set the value to `0` to record every occurrence. Added the metrics `sql.insights.cooldown.suppressed` and `sql.insights.cooldown.evictions` to monitor this behavior.
* Added an experimental Ubuntu-based Docker image alongside the existing Red Hat UBI-based Docker image. The Ubuntu-based image is available from the same registries with a `-noble` tag suffix.
* Reduced the default value of the `kv.range_merge.queue_interval` cluster setting (which controls how long the merge queue waits between processing replicas) from 5s to 200ms. This helps the merge queue keep up with range creation and reduces range accumulation.
* Added sixteen new SQL metrics for tracking `EXPLAIN` and `EXPLAIN ANALYZE` usage: `sql.explain.started.count` / `sql.explain.count`, `sql.explain_analyze.started.count` / `sql.explain_analyze.count`, plus four sub-counters (`sql.explain.procedure.count`, `sql.explain_analyze.procedure.count`, `sql.explain.udf.count`, `sql.explain_analyze.udf.count` and their `.internal` counterparts for internal queries) that fire when the `EXPLAIN` target is a `CALL` of a stored procedure or a statement that invokes a user-defined function. These statements were previously bucketed into the `sql.misc.*.count` series and were not visible individually.
* Added the `restore.wait_for_span_config_conformance.enabled` config property, which is recommended to meet data domiciling requirements if you use the `STRICT` option with `BACKUP`.
* Added per-store metrics that expose the CPU cost breakdown for the multi-metric allocator, including `immovable`, `amplification`, `replica`, `sql_dist`, `sql_gateway`, and `overhead_k`, to help investigate allocator-related CPU usage.
* Added the `admission.kv.cpu_time_token_filler.dampening.enabled` cluster setting to disable runnable-goroutine-based dampening in the CPU time token filler.
* Updated changefeed metric descriptions to indicate which metrics are not emitted by the default Kafka, Pub/Sub, and webhook (no-linger) sinks. Also clarified that Kafka batch-reduction metrics and internal retry metrics track different behaviors and are no longer co-mingled.
* Added the `mma.store.shedding_overload_level` metric, which reports the per-store MMA shedding severity used during rebalancing to determine whether a local store is a shedding candidate. The value is updated each rebalancing pass and ranges from 0 (low load) to 4 (urgent overload); values >= 3 indicate the store is considered overloaded for shedding purposes.
* Added the Physical Cluster Replication (PCR) metric `physical_replication.cluster.replication_lag`, which reports replication lag directly (without requiring manual calculation).
* Added the following files to statement bundles produced by `EXPLAIN ANALYZE (DEBUG)`:
  * `descriptors.json`, which contains pretty-printed descriptor JSON for each object referenced by the statement.
  * `schema_changes.txt`, which contains recent schema-change history for those objects.
* Improved the default batching mechanism for changefeeds to Kafka, Google Cloud Pub/Sub, and webhooks, including for multi-topic feeds (such as `EACH_FAMILY` changefeeds).
* The `no-linger` sink for changefeeds (the default for the kafka, webhook, and pubsub v2 sinks) no longer requires `Frequency` when `Messages` or `Bytes` is set. The no-linger sink interprets the `Flush.Frequency` sink config option as a minimum linger; a batch is held until its oldest event is at least `Frequency` old, coalescing events into fewer, larger requests at the cost of latency, for example `kafka_sink_config='{"Flush": {"Frequency": "1s"}}'`. Leave it unset for the lowest latency.

<a id="v26-3-0-command-line-changes" />

### Command-line changes

* Added the `--background` flag to `cockroach demo` to start the demo cluster without opening an interactive SQL shell. The process prints connection information and runs until it receives `SIGINT` or `SIGTERM`.
* Added a "restricted" mode for `cockroach sql`, which can be used with the `\restrict` and `\unrestrict` metacommands. In restricted mode, the shell blocks all backslash metacommands except `\unrestrict`, helping prevent metacommands embedded in plain-text SQL dumps from running when you pipe the dump into the shell. SQL statements still run normally, and restricted mode is preserved across `\i` and `\ir` includes.
* Added the `cockroach debug upload` command to upload a `debug.zip` file to Cockroach Labs Support.
* The `\\l+` and `\\dt+` metacommands in the built-in SQL shell now include a `Size` column showing on-disk sizes, matching PostgreSQL `psql`. These values are stored in a periodically refreshed cache that pulls from `pg_database_size` and `pg_table_size`, which may lag the live byte count by a few minutes.

<a id="v26-3-0-db-console-changes" />

### DB Console changes

* Added a "Changed only" checkbox to the Cluster Settings report page in DB Console. When checked, only settings that have been explicitly overridden are shown.

<a id="v26-3-0-bug-fixes" />

### Bug fixes

* Fixed `pg_catalog.pg_proc.prosecdef` and `information_schema.routines.security_type` to correctly report `DEFINER` for `SECURITY DEFINER` functions and procedures.
* Fixed a bug that could cause an internal error when running `SELECT ... FOR UPDATE` or `SELECT ... FOR SHARE` with `ORDER BY` that uses a non-default `NULL` ordering (for example, `null_ordered_last`) under `READ COMMITTED`.
* Fixed a bug where `BACKUP TENANT` could silently omit the data of tables located after a table that is excluded from backup and is the last table in the tenant's keyspace.
* `width_bucket(operand, b1, b2, count)` now returns an error when `count` is zero or negative, matching PostgreSQL.
* `width_bucket(operand, b1, b2, count)` now returns an error when the two bounds are equal, matching PostgreSQL.
* Fixed a bug where division between `FLOAT` and `INT` values (for example, `8.0::FLOAT / 2::INT`) could fail with an "unsupported binary operator" error. Mixed `FLOAT`/`INT` division now returns a `FLOAT` result, matching PostgreSQL behavior.
* Fixed a bug in encryption at rest where 256-bit keys generated with `cockroach gen encryption-key -version=2 --size=256` were misclassified as AES-192-CTR-V2 on load. This bug resulted in data keys being generated at 24 bytes (AES-192) instead of the intended 32 bytes (AES-256). Existing key files with the wrong algorithm string are now automatically corrected on load, and data keys are rotated to the correct size on node restart.
* Fixed a bug where `IMPORT INTO` and some schema change backfills could fail when the destination table had computed columns that call user-defined functions (UDFs). Computed columns that reference `IMMUTABLE`, single-expression SQL UDFs now work with `IMPORT INTO` and `ALTER TABLE ... ADD COLUMN`.
* Fixed a bug where `ALTER DATABASE ... PRIMARY REGION` could leave stale table zone configurations, causing voter constraints and lease preferences to reference the previous primary region.
* Fixed a bug where a CockroachDB node could hang during startup if a secondary tenant (shared-process) server failed to initialize, blocking the system tenant from accepting connections.
* Fixed a bug where `pg_index.indisready` was incorrectly set to `false` for all valid indexes. In PostgreSQL, `indisready` is `true` for all fully created indexes.
* Fixed a bug where restoring a database backup containing default privileges that referenced non-existent users would leave dangling user references in the restored database descriptor.
* Fixed a bug where rolling back a `CREATE TABLE` that referenced user-defined types or sequences would leave orphaned back-references on the type and sequence descriptors, causing them to appear in `crdb_internal.invalid_objects` after the table was GC'd.
* Fixed a bug where running `EXPLAIN ANALYZE (DEBUG)` on a query that invokes a UDF with many blocks could cause out-of-memory errors (OOMs).
* Fixed a bug where concurrent updates to a table using multiple column families during a partial index creation could result in data loss, incorrect `NULL` values, or validation failures in the resulting index.
* Fixed a bug where setting `AWS_SKIP_CHECKSUM=true` did not fully suppress checksums on multipart uploads to S3-compatible storage, which could cause errors with services that do not support `CRC` checksums.
* Added a new cluster setting `changefeed.kafka.max_request_size` and a per-changefeed `Flush.MaxBytes` option in the Kafka sink config to control the maximum size of record batches sent to Kafka by the v2 sink. Lowering this from the default of 256 MiB can prevent spurious message-too-large errors when multiple batches are coalesced into a single broker request.
* Fixed a bug where the DB Console Overview page could crash for users with the `MODIFYSQLCLUSTERSETTING` and `VIEWACTIVITY` privileges but without `VIEWCLUSTERSETTING`. The Settings API now always includes required non-sensitive console keys such as `version`.
* The PCR job now switches into the cutover phase more promptly after a failover is requested, terminating the replication phase more quickly and more reliably when components of the ingestion process are hung due to network errors.
* Fixed a bug where `ALTER FUNCTION ... RENAME TO` and `ALTER PROCEDURE ... RENAME TO` could create duplicate functions in non-public schemas.
* Fixed a data race that could cause certificate expiration metrics (`security.certificate.expiration.node-client`, `security.certificate.expiration.client-tenant`, `security.certificate.expiration.ca-client-tenant` and their TTL counterparts) to not update after certificate rotation via `SIGHUP`.
* Fixed a crash (`traceRegion: alloc too large`) that could occur when Go's execution tracer was enabled and a range cache lookup used a key longer than about 64 KB.
* Fixed a bug where descriptor version fetching could be incorrectly throttled by the elastic CPU limiter, potentially leading to increased query latency or timeouts under high CPU load.
* Context cancellation is now surfaced if a `statement_timeout` occurs while waiting for a schema change.
* Fixed a bug where owner columns for built-in objects in some `pg_catalog` tables (`pg_proc.proowner`, `pg_type.typowner`, `pg_collation.collowner`, `pg_operator.oprowner`, `pg_tablespace.spcowner`, and `pg_statistic_ext.stxowner`) could be `NULL`.
* Fixed a bug where transient I/O errors (such as cloud storage network timeouts) during split or merge trigger evaluation were misidentified as replica corruption, causing the node to crash. These errors now correctly fail the operation, which is retried automatically.
* Fixed a bug where transient I/O errors reading from the `AbortSpan` were misidentified as replica corruption, causing the node to crash. These errors are now returned to the caller as regular errors.
* Fixed a bug where comparing an `OID[]` column against a string constant could fail with the error `unsupported comparison operator: <oid[]> = <string>`, improving PostgreSQL compatibility (for example, for `pg_dump`).
* Fixed a bug where statement hints that set session variables with integer, float, or timeout types (for example, `reorder_joins_limit`, `testing_optimizer_cost_perturbation`, or `statement_timeout`) could be accepted but then silently ignored at execution time.
* Fixed a bug where converting a table from `REGIONAL BY ROW` to `GLOBAL` would not clear the `skip_unique_checks` storage parameter on the primary key, even though implicit partitioning was removed.
* Fixed a bug where executing a mutation in a subquery (e.g., as a CTE) could cause the "rows written" metrics like `sql.statements.index_rows_written.count` and `sql.statements.index_bytes_written.count` to not be incremented correctly.
* Fixed a bug that caused an "Unsupported system page size" startup crash with `ppc64le` (POWER) CockroachDB binaries on systems with `64 KB` pages.
* `REFRESH MATERIALIZED VIEW` now evaluates row-level security (RLS) policies using the view owner's identity instead of the invoker's, matching PostgreSQL's definer semantics.
* Fixed a bug where DB Console **Databases** page privilege checks did not resolve role membership chains for `CONNECT` grants. Users who inherited `CONNECT` through role hierarchies now correctly see their authorized databases and tables.
* Fixed a bug where the `lock_timeout` and `deadlock_timeout` session settings were not honored by FK existence checks performed during insert fast path execution. This could cause inserts to block indefinitely on conflicting locks instead of returning a timeout error.
* Fixed a bug where CockroachDB might not have respected the table-level parameters `sql_stats_automatic_full_collection_enabled` and `sql_stats_automatic_partial_collection_enabled` and defaulted to using the corresponding cluster settings when deciding whether to perform automatic statistics collection on a table.
* Fixed a bug where creating a PL/pgSQL or SQL user-defined function that referenced a schema-qualified sequence or table through a `REGCLASS` cast (for example, `nextval('sc.myseq'::REGCLASS)`) could fail with the error `relation does not exist` at `CREATE FUNCTION` time if the object's schema was not in the search path.
* Fixed a bug where logical replication job status messages showed a fully redacted error (`"permanent error: ‹×›"`). The actual error text is now preserved.
* Fixed a nil-pointer panic during tenant garbage collection that could occur when the zone configuration or GC policy for the tenants range was missing.
* Fixed a rare panic that could occur when a virtual cluster entry was removed before it was fully populated by the rangefeed.
* Fixed an assertion failure during `DROP SCHEMA CASCADE` or concurrent table drops when triggers had cross-table dependencies.
* Fixed a bug where `ALTER TABLE ... ADD CONSTRAINT ... UNIQUE` could ignore the `STORING` clause and create a unique index without the specified stored columns.
* Fixed a bug causing an internal error when creating a trigger on a `REGIONAL BY TABLE` or `GLOBAL` table, if the trigger function referenced the multi-region enum type `crdb_internal_region`.
* `ALTER FUNCTION RENAME` and `ALTER FUNCTION SET SCHEMA` now correctly detect dependencies from triggers and row-level security policies, preventing renames that would break those objects.
* Fixed a bug where `EXPORT INTO PARQUET` could panic when `chunk_size` or `chunk_rows` was set to a large value. This fix avoids an integer overflow in the Parquet library's buffer sizing logic.
* Trigonometric functions now return errors consistent with PostgreSQL when passed out-of-range input.
* Fixed a bug that could cause an infinite loop in the optimizer when a query used a `LIMIT` value larger than `4294967295` with a join.
* The `pg_catalog` and `information_schema` views no longer report a column default for identity columns, matching expected behavior in PostgreSQL.
* Fixed a bug where `experimental_strftime` could truncate years before 1000 when you used the `%Y` format directive. `experimental_strftime` now zero-pads `%Y` to at least four digits (for example, year 1 formats as `0001`).
* Fixed a bug under the declarative schema changer where `ALTER TABLE ... DROP CONSTRAINT {pk}, ADD PRIMARY KEY (...)` would leave behind an unwanted unique secondary index on the old primary key columns.
* Fixed a bug where the DB Console login page did not show the **OIDC login** button when navigating with `?cluster=<tenant>` to a tenant with OIDC enabled. OIDC login on non-default virtual clusters now works correctly.
* Fixed a bug where a malformed binary numeric value sent over the pgwire protocol could cause the server to panic with a slice bounds error, crashing the connection. These inputs are now rejected with a proper error.
* Fixed a bug that caused `PGCOPY` imports to reject valid octal and hexadecimal byte escapes for values greater than 127. `PGCOPY` imports now also treat the standard `\.` marker as the end of input.
* Fixed a bug where setting `--advertise-sql-addr` to the same value across multiple SQL instances could cause changefeeds with `execution_locality` filters to fail with "no instances found matching locality filter".
* A physical cluster replication reader tenant no longer fails authentication and other queries with errors of the form `resolved <name> to <id> but found no descriptor with id <id>` after the reader tenant ingests a system table at an ID different from the one it was bootstrapped with. Previously, a per-node namespace cache could pin the bootstrap-time ID and require a tenant restart to recover.
* Fixed a panic during `CREATE VECTOR INDEX` backfill when the table contained a public column ordered before the vector column that was not stored in the source primary index and was not referenced by the new index. In practice this was triggered by virtual computed columns. The schema change crashed the SQL node processing the backfill instead of completing.
* Stopped logging a spurious "declarative schema changer does not support DISCARD" message every time a `DISCARD` statement was executed. The message had no functional impact but could produce very high log volume on busy clusters that issue `DISCARD` on every connection checkout.
* Fixed a hang in `IMPORT INTO` rollback where the revert could wedge the job indefinitely until the node was restarted.
* Fixed a bug where unqualified function calls could fail with incorrect privilege errors when two databases on the same cluster had identically-named functions in custom schemas. The query cache could serve a memo from one database context to another, causing `USAGE` privilege errors referencing schemas from the wrong database.
* Fixed an issue where `ALTER TYPE ... DROP VALUE` could fail on enums referenced by very large tables when the validation scan ran long enough for GC to invalidate its read timestamp. The schema changer now installs a protected timestamp over referencing tables for the duration of the scan.
* Fixed a bug where `RESTORE TABLE` of a multi-region table backed up mid-`ALTER TABLE ... SET LOCALITY` would fail with a descriptor rewrite error.
* Fixed a bug where formatting a `UNIQUE` constraint or `PRIMARY KEY` that used both storage parameters (`WITH (...)`) and a `WHERE` clause or visibility clause could produce invalid SQL that CockroachDB could not parse.
* Fixed incorrect results in `ST_3DDistance`, `ST_3DDWithin`, `ST_3DShortestLine`, and `ST_3DClosestPoint` for polygons lying in a vertical plane, and improved detection of when a line passes through a 3D polygon's interior.
* Fixed a bug where Physical Cluster Replication (PCR) reader virtual clusters could permanently fail authentication, causing all SQL connections to fail with "descriptor not found".
* Fixed a bug where some scalar expressions could incorrectly fail type checking with "unsupported binary operator" errors. These expressions now proceed further through type checking, which can allow them to succeed or return a more accurate error message.
* Fixed a bug in `cockroach debug tsdump` where the dump output could omit labeled histogram child time series (for example, `changefeed.sink_backpressure_nanos{scope="default"}-count` and `changefeed.sink_backpressure_nanos{scope="default"}-p99`), even when the `timeseries.persist_child_metrics.enabled` cluster setting was enabled.
* Fixed a data race in the multi-metric allocator between gossip-driven store load updates and concurrent lease/replica rebalancing decisions.
* Fixed a bug that could cause `DROP COLUMN ... CASCADE` to return an error referencing an internal placeholder column name when concurrent writes occurred during a schema change.
* Fixed a bug where `make_date()`, `make_timestamp()`, and `make_timestamptz()` could return an incorrect year for negative (BC) year inputs. CockroachDB now interprets negative years as "N BC", matching PostgreSQL semantics.
* A long-running `BACKUP` to S3 using `AUTH=implicit` no longer fails with an `ExpiredToken` error when it races the rotation of the underlying short-lived credentials. The S3 client now retries `ExpiredToken`, `ExpiredTokenException`, and `RequestExpired` errors the same way the legacy `aws-sdk-go` v1 client did.
* The AWS S3 and KMS clients now refresh short-lived credentials a few seconds before they expire, rather than only after expiry. This avoids `ExpiredToken` errors that could occasionally fail long-running `BACKUP`, `RESTORE`, or other operations when running with `AUTH=implicit` against credentials providers that issue short-lived tokens.
* Columns produced by set-returning functions in FROM clauses can now be referenced using the function name as a qualifier (e.g. `SELECT jsonb_each_text.key FROM jsonb_each_text(j)`), matching PostgreSQL behavior.
* Fixed a job profiler assertion failure that could occur when requesting execution details for a job that was currently running.
* Fixed a rare nil pointer panic in the internal SQL executor.
* The `^` operator and the `power()` and `pow()` built-in functions now return an error (instead of `NaN` or `Inf`) for invalid float exponentiation, such as a negative base with a non-integer exponent or a zero base with a negative exponent. This matches PostgreSQL behavior and prevents invalid values from propagating to downstream functions, such as geospatial functions.
* Fixed a bug where dropping a table with `exclude_data_from_backup` enabled could cause a concurrent `BACKUP` holding a protected timestamp on the table's data to fail with the error `batch timestamp must be after replica GC threshold`.
* The `storage.compression.cr` metric now includes blob files.
* The `pg_get_function_arguments` and `pg_get_function_identity_arguments` built-in functions now include parameter names, parameter modes (`OUT` and `INOUT`), `VARIADIC` parameters, and `DEFAULT` clauses for user-defined routines, matching PostgreSQL behavior. Previously, these functions returned only comma-separated input argument types.
* Fixed a bug where using the pgwire extended query protocol to bind parameters (including enum types) to a prepared statement after rolling back to a savepoint could cause an internal error (`read sequence number is ignored after savepoint rollback`).
* Fixed a bug where using the pgwire extended query protocol to prepare a statement after rolling back to a savepoint could cause an internal error (`read sequence number is ignored after savepoint rollback`). This bug affected client drivers that use the `Parse` message (extended protocol) instead of simple query execution.
* Fixed a panic of the form `runtime error: index out of range [N] with length N` that could occur when running a `COPY FROM` concurrently with an `ALTER TABLE ADD COLUMN`.
* Fixed a bug where `IMPORT INTO` from Parquet files could cause cascading node crashes across a cluster when the specified column list excluded a non-last table column.
* Fixed a compatibility bug where casting an OID to `regclass`, `regproc`, `regprocedure`, or a user-defined `regtype` and then to text emitted only the bare entity name. CockroachDB now follows PostgreSQL and emits the schema-qualified name (`schema.name`, with each component quoted only when necessary) whenever the bare name would not resolve back to the same OID through the current `search_path`.
* `pg_function_is_visible` now matches PostgreSQL's signature-aware semantics: a function in a non-search-path schema whose signature is disjoint from same-named functions in earlier search-path schemas is correctly reported as visible (`true`) rather than shadowed.
* Fixed a `no stores for meansForStoreSet` panic in the multi-metric allocator (MMA) store rebalancer that could occur when the rebalancer's periodic tick raced gossip propagation of store descriptors during process startup. The rebalancer now skips its work for that tick and retries on the next interval.
* Fixed a limitation/warning for Active Session History (ASH) when enabled on high-core nodes.
* Fixed a crash that could occur when `st_linemerge` was called on invalid geometries that contain `NaN` coordinates.
* Fixed a bug where queries calling a UDF that transitively performs mutations could incorrectly use a LeafTxn, potentially leading to incorrect behavior.
* Fixed some node-to-node connection error paths during `DistSQL` execution to correctly return error code `58C01` (InternalConnectionFailure) instead of `XXUUU` (Uncategorized).
* Fixed an internal optimizer assertion that could cause query planning to fail, most notably during `INSPECT` index consistency checks, with an "estimated distinct count must be non-zero" error when the optimizer encountered inconsistent column statistics.
* Fixed a bug in the legacy replica allocator where ranges with `voter_constraints` discriminating sibling stores on the same node could get stuck on the wrong sibling indefinitely.
* Fixed a bug where logging large-row events could cause excessive memory usage and potentially lead to out-of-memory (OOM) crashes when a single statement wrote many rows larger than the `sql.guardrails.max_row_size_log` threshold (particularly with wide primary keys). The logged primary key is now size-bounded, and large-row event logging is rate-limited with `SkippedLargeRows` reporting suppressed events.
* Fixed a bug that could cause an internal error when executing a prepared statement with an untyped `NULL` argument.
* Fixed a regression where the Jobs page could display duplicate titles when embedded in the CockroachDB Cloud Console with the new navigation enabled.
* Fixed a bug where the DB Console on virtual clusters showed zero values for histogram-based charts (such as Service Latency and SQL Execution Latency) even though the metrics were being recorded correctly. Prior to this fix, the metrics were visible in Prometheus scrapes (`/_status/vars`) and Datadog but not in the DB Console.
* Fixed a bug where `ALTER DATABASE system DROP REGION` could fail with the error `unsupported comparison: bytes to crdb_internal_region` when the `system` database was configured as multi-region.
* DDL and `current_user` inside a `SECURITY DEFINER` stored procedure now resolve against the procedure owner, matching PostgreSQL. Previously they evaluated against the invoker.
* Fixed a bug where `pg_class.relhastriggers` always reported `false`, even for tables that had triggers defined.
* Fixed a bug that could cause an internal error during `INSERT`, `UPDATE`, or `UPSERT` on a `REGIONAL BY ROW` table configured with `infer_rbr_region_col_using_constraint` when the referenced parent table did not have a unique constraint covering the foreign key's non-region columns. The region is now inferred by selecting an arbitrary matching parent row.
* Fixed a bug where creating a user-defined function or view could fail with an internal error if its body contained `NULL::REGCLASS` or a `REGCLASS` parameter that evaluated to `NULL`.
* Fixed a planning issue where queries that use `FOR UPDATE SKIP LOCKED` with `ORDER BY` on an unindexed column and `LIMIT` could lock more rows than necessary. This could cause concurrent sessions to skip those rows and return fewer results than expected. These queries now lock only the rows needed to satisfy the `LIMIT`.
* Fixed a bug where a `DELETE` or `UPDATE` that triggered a foreign key cascade could hit an internal assertion error ("execution requires all update columns have a fetch column") if a concurrent `ALTER TABLE ... ADD COLUMN` or `ALTER TABLE ... DROP COLUMN` modified the child table while the cascade was running.
* Fixed a bug where `DROP TABLE ... CASCADE` could fail when another `REGIONAL BY ROW` table referenced it via a foreign key used to infer its region column with the `infer_rbr_region_col_using_constraint` storage parameter.
* Fixed a bug where incremental backups could fail when the collection URI had no path component (for example, `gs://my-bucket?AUTH=...`).
* Fixed a bug where `INSERT`, `UPSERT`, and `UPDATE` statements could fail with the error "Access to crdb\_internal and system is restricted" while adding an expression index, adding a computed column, or changing a column's type, even when the statement did not reference `crdb_internal`. This error occurred only while the schema change was in progress.
* Fixed a bug that could cause a node to crash with a `transaction unexpectedly finalized` error when a transaction disabled write buffering mid-transaction and then issued `ROLLBACK` while buffered writes were still pending.
* Fixed a bug where a node whose `store descriptor` stopped being propagated via gossip was not kept in the `suspect` state for the configured interval, which could cause a flapping node to be reported as `live` and rejoin service prematurely.
* Fixed a bug where `FETCH FIRST` on an empty `WITH HOLD` cursor could return an internal error.
* Fixed a bug where concurrent role DDL (for example, `CREATE USER`, `ALTER ROLE`, `DROP USER`, `GRANT`, and `REVOKE`) could leave orphaned lease records and block subsequent role schema changes until the affected node was restarted.
* Fixed bugs in the DB Console `All sources` view that caused incorrect time-series metric totals in deployments using virtual clusters:
  * Application/SQL metrics now show the sum across all virtual clusters.
  * Several store metrics are no longer double-counted or incorrectly scoped to a tenant.
* Fixed issues in multi-tenant deployments (virtual clusters) where the DB Console `All sources` view displayed incorrect metric values, including:
  * Missing totals for application- and SQL-level metrics across tenants.
  * Double-counting some store metrics, such as per-replica CPU and intent age.
* Fixed a bug where running `ALTER SEQUENCE IF EXISTS` with the `SEQUENCE NAME` option on a non-existent sequence could cause a panic.
* Fixed a bug where the `STANDBY READ TS POLLER` job on a Physical Cluster Replication (PCR) standby cluster could get stuck waiting for the system `public` schema descriptor (ID 29) lease to expire.
* Fixed a bug where the row-count verification run after `IMPORT` could double-count rows that straddle span boundaries.
* Fixed a bug where a schema change could hang indefinitely if a node's clock lagged behind the commit timestamp of a new descriptor version. The lease manager now retries reading the new version instead of abandoning lease cleanup.
* Fixed a bug where a changefeed using a CDC expression (for example, `CREATE CHANGEFEED ... AS SELECT ...`) could fail during certain schema changes (adding a computed column, creating an expression index, or changing a column type) with an error like `function "crdb_internal.assignment_cast" unsupported by CDC`, even when the changefeed expression did not reference the new column.
* Fixed a bug where `pg_catalog.pg_enum` could expose enum values that were still being added by a schema change and were not yet usable. This could cause confusing errors for tools that read enum values from `pg_enum` and then attempted to use them in queries.
* Fixed a race condition in the SQL lease manager where a schema change could leave a stale lease record in `system.lease`, causing later lease acquisitions for that descriptor to fail with a "failed to insert lease" error until the stale record was cleaned up.
* Fixed an issue where a partitioned index scan on a JSONB column could generate an empty key span and fail with `end key must be greater than start`.
* Fixed a bug where Avro-enriched changefeed messages could emit stale values for `source.ts_ns`, `source.ts_hlc`, and `source.mvcc_timestamp`, repeating the timestamp from an earlier event instead of the current event.
* Fixed a bug that could cause an initial-scan changefeed with `diff` enabled, or a CDC query using `cdc_prev`, to fail with a GC threshold error if garbage collection ran during the scan.
* Fixed a bug where importing Avro data with large records could cause unbounded memory growth, slowing or stalling the import. Such records now fail with a clear error suggesting that the `max_row_size` option be increased.
* Fixed a bug where `CREATE TEMPORARY TABLE` could fail with an internal error in the same session after the session's first temporary table creation was rolled back.
* Fixed a bug where `DISTINCT ON` queries with `ORDER BY` could return incorrect results during distributed execution when the operation spilled to disk (for example, due to memory pressure).
* Fixed a bug where `CREATE SCHEMA` with an empty quoted schema name (for example, `""`) could trigger an internal error. It now returns a user-facing syntax error.
* Fixed a bug where binding a prepared `EXPLAIN` or `EXPLAIN ANALYZE` statement could fail with the error "EXPLAIN ANALYZE can only be used as a top-level statement".
* Improved the multi-metric allocator (MMA) (preview) to exclude down, dead, or decommissioned stores when computing cluster-wide load averages for overload detection. This prevents stale load from unavailable stores from skewing rebalancing decisions.
* Fixed an issue in Logical Data Replication (LDR) where last-write-wins (LWW) conflict resolution could incorrectly drop a newer incoming row. This could happen when replicating tables with rows larger than \~3 KiB, where paginated reads might omit the MVCC origin timestamp required for conflict resolution.
* Fixed an issue that could cause a lost write when a transaction used buffered writes, set `lock_timeout`, and attempted to continue after a lock-timeout error by issuing `ROLLBACK TO SAVEPOINT`.
* Fixed an issue where calling a procedure, or invoking a user-defined function (UDF) in a query, in the same implicit-transaction statement batch that created or replaced it could fail with a "function does not exist" error if the transaction was automatically retried.
* Fixed an issue where `cockroach debug job-trace` could fail with "column "trace\_id" does not exist". If no trace is available for the requested job, the command now reports that clearly.
* Fixed an issue where the merge queue could repeatedly re-process ranges that could not be merged (for example, when the right-hand neighbor had an unexpired sticky bit), potentially starving other mergeable ranges. The merge queue now backs off before retrying an unmergeable range; this cooldown is configurable via the `kv.range_merge.cooldown` cluster setting.
* Fixed a rangefeed memory accounting leak where, if a client's gRPC stream failed, budget for events that were dequeued but not yet sent was not returned to the per-range rangefeed memory budget. Repeated stream failures could cause rangefeed consumers to be disconnected with spurious `budget exceeded` errors.
* Fixed an issue where a transaction using buffered writes could return incorrect (including misordered) results for some queries that combine range scans and point lookups, particularly when a scan limit was reached and some point lookups were served from buffered data. Also fixed an issue where request statistics (keys/bytes read) could be underreported for point lookups served from buffered data.
* Fixed an issue where `pg_advisory_xact_lock` or `pg_advisory_xact_lock_shared` could be silently lost while a transaction was still open (for example, during a range split or lease transfer) when write buffering was enabled. This could allow another session to acquire the same advisory lock concurrently.
* Fixed a bug where large Raft commands (for example, AddSSTable operations used by index backfills, IMPORT and RESTORE) could be starved indefinitely on ranges with concurrent foreground writes. This could trip the per-replica circuit breaker and surface "replica unavailable ... slow proposal" errors even though the range was otherwise healthy.

<a id="v26-3-0-performance-improvements" />

### Performance improvements

* Fixed a performance bug that could prevent query plans containing user-defined functions (UDFs) from being cached. Repeated executions of prepared statements that reference UDFs now have less planning overhead.
* Improved performance in some prepared statements by storing inline placeholder equalities into other references of the same column, such as correlated subqueries. This change enables constrained index scans in prepared statements where previously a full scan was required.
* Statement executions using canary stats will no longer use cached plans, which prevents cache thrashing but causes a slight increase in planning time over statement executions using stable stats.
* Improved performance of queries against `information_schema.routines` when looking up role information for built-in functions.
* Fixed a bug that caused pretty-printing of large SQL statements to fail due to exceeding max recursion depth.
* Improved throughput for the Kafka v2 changefeed sink by increasing the per-broker limit to 5 concurrent produce requests, matching the v1 behavior.
* Fixed a bug where the optimizer could fail to generate efficient generic plans for queries whose filters reference placeholders and also include subqueries (such as `EXISTS`, scalar subqueries, or `ANY`) that reference the same placeholder.
* Improved performance for concurrent protected `timestamp` protect and release calls, such as those used by `BACKUP` operations and changefeeds.
* Enabled parallelization of multi-key lookup joins for all lookup joins by default, improving performance of general mutation statements. Previously, this behavior applied only to mutations on multi-region tables. To restore the previous behavior, set `parallelize_multi_key_lookup_joins_only_on_mr_mutations` to `true`.
* Improved performance on queries that use `ORDER BY` with `LIMIT` on hash-sharded secondary indexes by using an efficient `UNION ALL` plan instead of falling back to a full table scan. Previously, either an index hint or `SET unconstrained_non_covering_index_scan_enabled = on;` were required to use an efficient `UNION ALL` plan when the index was non-covering.
* Improved performance for SQL routines, including user-defined functions (UDFs) and procedures, by deferring routine body construction to execution time instead of planning time.
* Improved planning performance for queries against tables with user-defined `ENUM` columns.
* Improved rangefeed performance by prioritizing catch-up scans for high-priority rangefeeds (such as system table watchers) over bulk consumers, reducing delays in delivering updates under heavy scan load.

## v26.3.1

Release Date: August 21, 2026

<a id="v26-3-1-downloads" />

### Downloads

<Note>
  Experimental downloads are not qualified for production use and not eligible for support or uptime SLA commitments, whether they are for testing releases or production releases.
</Note>

<table><thead><tr><th>Operating System</th><th>Architecture</th><th>Full executable</th><th>SQL-only executable</th></tr></thead><tbody><tr><td rowspan="2">Linux</td><td>Intel</td><td><a href="https://binaries.cockroachdb.com/cockroach-v26.3.1.linux-amd64.tgz">cockroach-v26.3.1.linux-amd64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-v26.3.1.linux-amd64.tgz.sha256sum">SHA256</a>)</td><td><a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.1.linux-amd64.tgz">cockroach-sql-v26.3.1.linux-amd64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.1.linux-amd64.tgz.sha256sum">SHA256</a>)</td></tr><tr><td>ARM</td><td><a href="https://binaries.cockroachdb.com/cockroach-v26.3.1.linux-arm64.tgz">cockroach-v26.3.1.linux-arm64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-v26.3.1.linux-arm64.tgz.sha256sum">SHA256</a>)</td><td><a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.1.linux-arm64.tgz">cockroach-sql-v26.3.1.linux-arm64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.1.linux-arm64.tgz.sha256sum">SHA256</a>)</td></tr><tr><td rowspan="2">Mac<br />(Experimental)</td><td>Intel</td><td><a href="https://binaries.cockroachdb.com/cockroach-v26.3.1.darwin-10.9-amd64.tgz">cockroach-v26.3.1.darwin-10.9-amd64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-v26.3.1.darwin-10.9-amd64.tgz.sha256sum">SHA256</a>)</td><td><a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.1.darwin-10.9-amd64.tgz">cockroach-sql-v26.3.1.darwin-10.9-amd64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.1.darwin-10.9-amd64.tgz.sha256sum">SHA256</a>)</td></tr><tr><td>ARM</td><td><a href="https://binaries.cockroachdb.com/cockroach-v26.3.1.darwin-11.0-arm64.tgz">cockroach-v26.3.1.darwin-11.0-arm64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-v26.3.1.darwin-11.0-arm64.tgz.sha256sum">SHA256</a>)</td><td><a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.1.darwin-11.0-arm64.tgz">cockroach-sql-v26.3.1.darwin-11.0-arm64.tgz</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.1.darwin-11.0-arm64.tgz.sha256sum">SHA256</a>)</td></tr><tr><td>Windows<br />(Experimental)</td><td>Intel</td><td><a href="https://binaries.cockroachdb.com/cockroach-v26.3.1.windows-6.2-amd64.zip">cockroach-v26.3.1.windows-6.2-amd64.zip</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-v26.3.1.windows-6.2-amd64.zip.sha256sum">SHA256</a>)</td><td><a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.1.windows-6.2-amd64.zip">cockroach-sql-v26.3.1.windows-6.2-amd64.zip</a><br />(<a href="https://binaries.cockroachdb.com/cockroach-sql-v26.3.1.windows-6.2-amd64.zip.sha256sum">SHA256</a>)</td></tr></tbody></table>

### Docker image

[Multi-platform images](https://docs.docker.com/build/building/multi-platform) include support for both Intel and ARM. Multi-platform images do not take up additional space on your Docker host.

Within the multi-platform image, both Intel and ARM images are **generally available** for production use.

To download the Docker image:

```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
docker pull cockroachdb/cockroach:v26.3.1
```

<a id="v26-3-1-security-updates" />

### Security updates

* The cluster.secret, enterprise.license,
  and cloudstorage.http.custom\_ca cluster settings are now marked
  sensitive: their values are redacted for users without the
  MODIFYCLUSTERSETTING privilege when
  server.redact\_sensitive\_settings.enabled is set, and are suppressed
  from diagnostics artifacts. The sql.override.allow\_unsafe\_internals.enabled
  setting is no longer marked sensitive.
* The settings.json file in debug zips
  and the DB Console settings page no longer show the values of
  sensitive cluster settings (such as authentication secrets), even for
  users with the MODIFYCLUSTERSETTING privilege and even in debug zips
  collected without the --redact flag. Sensitive values are replaced
  with `'<redacted>'`. Privileged users can still read these values with
  SHOW CLUSTER SETTING.
* The events.json file in debug zips no
  longer contains statement placeholder values, which can carry
  sensitive data such as passwords or sensitive cluster setting values
  bound via prepared statements. The value recorded for ALTER TENANT ...
  SET CLUSTER SETTING events is now also hidden there, matching regular
  setting-change events.
* Unredacted debug zips no longer
  contain the values of sensitive cluster settings (such as
  authentication secrets). This covers the system.settings,
  system.tenant\_settings and system.eventlog dumps, the cluster
  settings history, and session/query dumps whose statement text
  references a sensitive setting. Values for settings not known to the
  current version are conservatively redacted as well. Unredacted zips
  also no longer contain the connection details of external
  connections or the execution arguments of scheduled jobs, both of
  which can hold cloud storage credentials.
* CREATE ROLE and ALTER ROLE statements
  that supply the password via a bound placeholder (e.g. a prepared
  statement executed with arguments, the typical driver behavior) no
  longer record the raw password in the system event log or SQL logs;
  the recorded placeholder value is '\*\*\*\*\*', matching the existing
  treatment of password literals. Debug zips collected without --redact
  also scrub bound placeholder values of historical role-change events
  from the event log dump and from log files.
* The set\_cluster\_setting and
  set\_tenant\_cluster\_setting events written to system.eventlog no longer
  record the values of sensitive cluster settings (such as
  authentication secrets). The event's value field contains `'<redacted>'`
  instead; resets are still recorded as DEFAULT.
* Debug zips collected without --redact
  and the output of `cockroach debug merge-logs` no longer include log
  entries that reveal the values of sensitive cluster settings (such as
  authentication secrets). Affected entries are redacted in place when
  possible (requires redactable logs, the default) and replaced with a
  tombstone otherwise. Entries that mention a sensitive setting without
  containing its value are scrubbed as well. When any such scrubbing
  occurs, the debug zip contains a sensitive\_settings\_warning.txt file
  describing what was removed.
* The error reported when the
  cloudstorage.http.custom\_ca cluster setting holds an unparsable
  certificate no longer includes the setting's value.

<a id="v26-3-1-sql-language-changes" />

### SQL language changes

* Support `EXPLAIN ANALYZE` showing execution
  details of body statements for PL/pgSQL sub-routines.
* The `transaction_rows_read_err` guardrail now
  accounts for rows read by earlier statements in the same explicit
  transaction when limiting how many rows a later statement may read. This
  causes a transaction that will exceed the limit to fail sooner, avoiding
  wasted work, rather than allowing each statement to read up to the full
  limit before erroring.

<a id="v26-3-1-bug-fixes" />

### Bug fixes

* Fixed a bug where the query optimizer lost
  histogram statistics when reading from a Common Table Expression (CTE),
  causing inaccurate row count estimates for filters like LIKE and range
  comparisons on CTE output. This could lead to the optimizer choosing
  inefficient join strategies. The fix is gated behind the
  optimizer\_use\_histograms\_for\_with\_scans session setting.
* Fixed a bug where execution statistics (such
  as contention time, CPU time, and MVCC iterator statistics)
  accumulated by earlier attempts of an automatically retried
  transaction were discarded. Execution statistics, KV CPU time, and
  idle latency in crdb\_internal.transaction\_statistics and the DB
  Console SQL Activity page, as well as the sql.txn.contended.count
  metric, now cover all execution attempts of a transaction, consistent
  with the transaction's service latency and retry count. Data-volume
  statistics — rows read/written, bytes read, and rows affected —
  continue to reflect the transaction's final attempt.
* Fixed a bug where a logical data replication
  (LDR) stream created by a non-admin user could fail to write to its
  dead letter queue with a privilege error.
* Fixed a bug where disabling or re-enabling an
  external statement hint via
  `information_schema.crdb_enable_statement_hints` did not invalidate
  cached query plans that depended on the hint.
* Fixed a bug where the chunk\_size option to
  EXPORT INTO PARQUET was ignored, so files were rotated only by
  chunk\_rows. EXPORT now bounds the approximate size of each parquet
  file as documented.
* Fixed an internal error ("inconsistent Case
  return types") that could occur when a CASE expression referenced a
  column produced by unnesting a constant array whose only element was a
  typed NULL, such as `unnest(ARRAY[NULL]::TEXT[])`.
* Fixed a bug where a node configured with a TLS
  cipher-suite allowlist (via the tls-cipher-suites flag) could stop
  accepting new TLS connections when a single client handshake stalled.
  Cipher-suite enforcement held a process-global lock across the TLS
  handshake, so one stalled handshake serialized all new SQL, HTTP, and
  RPC connection attempts on the node. This bug was present in v24.1.18,
  v24.3.14, v25.1.7, v25.2.0, v25.3.0, and all later releases on those
  branches.
* Fixed a bug where planning a query with a
  filter that negates a wide disjunction (e.g. `WHERE NOT (b1 OR ... OR bN)`)
  could consume very large amounts of memory and potentially crash the node,
  before any rows were read. The optimizer now bounds this work via the new
  `optimizer_max_disjunction_split_count` session variable.
* Fixed a bug where ALTER RANGE ... RELOCATE and
  ALTER RANGE ... RELOCATE LEASE loaded every range descriptor in the
  cluster into memory on each statement, even when relocating a single
  range. On clusters with a very large number of ranges, running many
  concurrent RELOCATE statements through a single node could exhaust memory
  and crash that node. These statements now read only the descriptors for
  the ranges being relocated.
* `CREATE TABLE ... AS <query>` with data inside a
  stored procedure is now rejected with a clear 'not supported' error at
  procedure-creation time, instead of failing at call time with a confusing
  'table is being added' error. `CREATE TABLE ... AS ... WITH NO DATA` and
  plain `CREATE TABLE` remain supported inside stored procedures.
* Fixed a rare race condition that could crash a
  node with a "pebble: batch already committing" error when a query
  buffered results that spilled to disk and then read them back
  concurrently (e.g. a query referencing a materialized common table
  expression multiple times).
* Fixed a bug where `SHOW BACKUPS` would not
  include a leading slash when listing from prefixless collection URIs.
* Fixed a bug where SHOW CREATE FUNCTION (and other
  schema output) could emit non-parseable SQL for a routine with a parameter
  named "index" whose type normalizes to a parenthesized form (e.g. BIT
  VARYING(61) ARRAY).
* Fix a bug that could result in a transaction being unexpectedly committed by transaction recovery, resulting in a transaction status error with `REASON_TXN_COMMITTED` when the transaction attempts to commit.
* Fix a bug where a range split could drop the
  rolled-back sequence number information associated with an unreplicated
  lock, potentially allowing a lock needed by a transaction to be
  released early after a savepoint rollback.
* Fixed a bug where a prepared statement issued
  over the PostgreSQL extended wire protocol that referenced a
  user-defined type such as an enum could fail with an internal error
  ("comparison of two different versions of enum") when the type's
  definition changed between preparation and execution.
* Fixed a bug where a changefeed could fail
  permanently with "cannot create external storage before init" if one of
  its aggregators was scheduled onto a node that was still starting up.
  The condition is transient and is now retried.
* Fix a bug that could result in a
  linearizability violation in some rare circumstances.
* Statement bundles collected via EXPLAIN
  ANALYZE (DEBUG) and statement diagnostics no longer include the values
  of sensitive cluster settings (such as authentication secrets) in
  env.sql, even when collected by a privileged user. A comment indicates
  when a sensitive setting differs from its default.
* Fixed a bug (introduced in 26.3) that caused an
  internal error ("unknown PG name for oid") when calling
  `pg_get_function_arguments` or `pg_get_function_identity_arguments` on
  certain array built-in functions. This could crash catalog-introspection
  queries that scan all functions.

## v26.3.2

Release Date: September 18, 2026

### Downloads

<Note>
  This version is currently available only for select CockroachDB Cloud clusters. To request to upgrade a CockroachDB self-hosted cluster to this version, [contact support](https://support.cockroachlabs.com/hc/requests/new).
</Note>

### Security updates

* String-valued group claims for JWT, OIDC, and OAUTHBEARER authorization are now split on commas only when synchronizing SQL role memberships. Identity providers that convey multiple groups should return a JSON array.
* Statements that reference external URIs, connection strings, or encryption passphrases that can embed credentials—such as `BACKUP`, `RESTORE`, `IMPORT`, `EXPORT`, changefeed, `EXTERNAL CONNECTION`, and replication statements—are now treated as possibly carrying a secret, so their constants, bound placeholder values, and recorded errors are kept out of diagnostics artifacts (for example, `SHOW QUERIES`, recorded SQL event details, and statement statistics). The full error is still returned to the client.
* Fixed redaction of connection URIs that can embed credentials in the statement text of `CREATE CHANGEFEED ... AS SELECT`, `CREATE SCHEDULE FOR CHANGEFEED`, `CREATE LOGICAL REPLICATION STREAM`, and `CREATE LOGICALLY REPLICATED`. Such URIs now render as `*****` in contexts like `SHOW CREATE SCHEDULE`.
* Statement diagnostic bundles no longer leak cleartext passwords or credential-bearing URIs (for example, from `BACKUP`, `RESTORE`, `IMPORT`, `EXPORT`, and `CREATE/ALTER ROLE ... WITH PASSWORD`) in the formatted statement; these are now redacted by default.
* The `schema_uri` option of `IMPORT`, which is used with the `AVRO` data format and can reference cloud storage with embedded credentials, is now redacted in formatted statements and has its secret query parameters redacted in `IMPORT` job descriptions, matching the existing handling of the `IMPORT` data file URIs.
* The source connection URI in `CREATE VIRTUAL CLUSTER ... FROM REPLICATION` and `ALTER VIRTUAL CLUSTER ... START REPLICATION` statements is now redacted by default when the statement is reformatted for display, matching other URI-bearing statements. Job descriptions continue to show the sanitized URI.

### Bug fixes

* Fixed a bug where a statement that hit `statement_timeout` inside an explicit transaction left the transaction in an unrecoverable aborted state. The transaction can now be recovered with `ROLLBACK TO SAVEPOINT`, matching PostgreSQL's behavior.
* `BACKUP`, `RESTORE`, and `SHOW BACKUPS` no longer send a multi-character listing delimiter to object stores. Backup collections hosted on S3-compatible stores that accept only single-character delimiters, such as Alibaba OSS, previously failed with `InvalidArgument: The length of delimiter must be 1`.
* Fixed a bug where setting up a PCR readable standby (read-only virtual cluster) failed to initialize its catalog when the replicated cluster contained a materialized view, leaving the standby's read-only virtual cluster unable to start.
* Fixed a bug where a failed backup compaction job would leave behind a `BACKUP-LOCK` file on external storage, blocking all subsequent compaction attempts to the same destination until the lock was manually removed or a new full backup started a new chain.
* Changefeeds created using `WITH diff` can be configured to stop emitting delete messages whose before and after images are both null by setting `changefeed.suppress_empty_deletes.enabled`. These messages could appear on `REGIONAL BY ROW` tables under `READ COMMITTED` or `SNAPSHOT` isolation, when deleting a row that did not exist from a table with no secondary indexes.
* Fixed a data race, introduced in v25.4, that could cause a node to crash when automatic statistics setting overrides for a table were updated concurrently with query planning. The race was only possible when the `sql.log.scan_row_count_misestimate.enabled` cluster setting was enabled; it is disabled by default.

### Performance improvements

* Added the `cloudstorage.gs.transport_shard_count` cluster setting. When set above `1`, CockroachDB shards reads from Google Cloud Storage across that many HTTP connections, which can substantially increase read throughput for bulk operations such as online restore. Defaults to `0` (a single connection, as before).
* The cluster setting `sql.stats.enum_type_rehydration.enabled`, which is disabled by default, allows cached table statistics to be updated in place when an enum type used by the table changes version, instead of being re-read from `system.table_statistics` on every node.
* Adding or renaming a value of a user-defined enum type, renaming the type, or creating a table that uses it no longer causes the table statistics of every table referencing that type to be re-read on every node. This behavior is gated by the cluster setting `sql.stats.enum_type_rehydration.enabled`.
