Database/xyz/Functions/fn_UpdateAsset.sqlmodified
--liquibase formatted sql
--changeset agentneo:fn_UpdateAsset runOnChange:true stripComments:false endDelimiter:/
--comment: Create or replace xyz."fn_UpdateAsset"

------------------------------------------------------------------------------------
-- Created by:  AgentNeo
-- Created on:  10/07/2026
-- Description: Update the mutable columns of an Asset and return the updated row.
--              Applies RFC 7396 merge-patch semantics: a NULL parameter means the
--              field was omitted and is left unchanged. Patchable columns are
--              "AssetTypeId", "ParentAssetId" and "Name". Because "ParentAssetId" is
--              nullable, the caller distinguishes "omitted" from "explicit null" via
--              _clearParentAssetId — when TRUE, "ParentAssetId" is set to NULL and
--              _parentAssetId is ignored. Identity/audit columns are out of scope.
--              Validates that _assetTypeId exists in the project and that
--              _parentAssetId points at a live asset in the same project and is not
--              the asset being updated. Resolves the shard id via
--              fn_GetProjectShardId (raises if the project does not exist). Returns
--              zero rows if no matching, non-deleted asset exists so the API layer
--              can map to 404.
------------------------------------------------------------------------------------

DROP FUNCTION IF EXISTS xyz."fn_UpdateAsset";

CREATE OR REPLACE FUNCTION xyz."fn_UpdateAsset" (
    _projectId           UUID,
    _assetId             UUID,
    _assetTypeId         UUID,
    _parentAssetId       UUID,
    _clearParentAssetId  BOOLEAN,
    _name                TEXT,
    _lastModifiedBy      TEXT
)
RETURNS TABLE (
    "AssetId"        UUID,
    "ProjectId"      UUID,
    "AssetTypeId"    UUID,
    "ParentAssetId"  UUID,
    "Name"           TEXT,
    "CreatedBy"      TEXT,
    "InsertedOn"     TIMESTAMP WITH TIME ZONE,
    "LastModifiedOn" TIMESTAMP WITH TIME ZONE,
    "LastModifiedBy" TEXT,
    "IsDeleted"      BOOLEAN,
    "DeletedOn"      TIMESTAMP WITH TIME ZONE,
    "DeletedBy"      TEXT
)
AS $$
DECLARE
    -- project id map
    _projectShardId INT;
BEGIN
    -- get shard id from uuid
    SELECT xyz."fn_GetProjectShardId"(_projectId) INTO _projectShardId;

    -- validate the asset type exists in this project
    IF _assetTypeId IS NOT NULL AND NOT EXISTS (
        SELECT 1 FROM xyz."AssetType" AS at
        WHERE at."ProjectShardId" = _projectShardId
          AND at."AssetTypeId"    = _assetTypeId
    ) THEN
        RAISE EXCEPTION 'AssetType with id % not found in project', _assetTypeId;
    END IF;

    -- validate the parent asset exists in this project, is not deleted, and is not the asset itself
    IF _clearParentAssetId IS NOT TRUE AND _parentAssetId IS NOT NULL THEN
        IF _parentAssetId = _assetId THEN
            RAISE EXCEPTION 'Asset % cannot be its own parent', _assetId;
        END IF;

        IF NOT EXISTS (
            SELECT 1 FROM xyz."Asset" AS pa
            WHERE pa."ProjectShardId" = _projectShardId
              AND pa."AssetId"        = _parentAssetId
              AND pa."IsDeleted"      = FALSE
        ) THEN
            RAISE EXCEPTION 'ParentAsset with id % not found in project', _parentAssetId;
        END IF;
    END IF;

    UPDATE xyz."Asset" a
    SET
        "AssetTypeId"    = COALESCE(_assetTypeId, a."AssetTypeId"),
        "ParentAssetId"  = CASE
                               WHEN _clearParentAssetId IS TRUE THEN NULL
                               ELSE COALESCE(_parentAssetId, a."ParentAssetId")
                           END,
        "Name"           = COALESCE(_name, a."Name"),
        "LastModifiedOn" = TIMEZONE('UTC', NOW()),
        "LastModifiedBy" = _lastModifiedBy
    WHERE a."ProjectShardId" = _projectShardId
      AND a."AssetId"        = _assetId
      AND a."IsDeleted"      = FALSE;

    -- explicit column list: fn_GetAsset also returns "SystemId", which is out of
    -- scope for the Asset write response, so it is intentionally not projected here
    RETURN QUERY
    SELECT
        g."AssetId",
        g."ProjectId",
        g."AssetTypeId",
        g."ParentAssetId",
        g."Name",
        g."CreatedBy",
        g."InsertedOn",
        g."LastModifiedOn",
        g."LastModifiedBy",
        g."IsDeleted",
        g."DeletedOn",
        g."DeletedBy"
    FROM xyz."fn_GetAsset"(_projectId, _assetId) g;
END;
$$
LANGUAGE plpgsql;
/