diff --git a/backend/.sqlx/query-8b62d25c98a6b3db3a0b7039cacf79799e6d7c7c6c9532545844bec9ec314516.json b/backend/.sqlx/query-e9efda2d1ab6b111103cfcd626cd91e2241721e34586dedf7bdacb83b732f2b7.json similarity index 55% rename from backend/.sqlx/query-8b62d25c98a6b3db3a0b7039cacf79799e6d7c7c6c9532545844bec9ec314516.json rename to backend/.sqlx/query-e9efda2d1ab6b111103cfcd626cd91e2241721e34586dedf7bdacb83b732f2b7.json index 1218b5b2ec..99a4c12462 100644 --- a/backend/.sqlx/query-8b62d25c98a6b3db3a0b7039cacf79799e6d7c7c6c9532545844bec9ec314516.json +++ b/backend/.sqlx/query-e9efda2d1ab6b111103cfcd626cd91e2241721e34586dedf7bdacb83b732f2b7.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO draft (workspace_id, path, typ, value, created_at, email, base)\n SELECT $2::text, path, typ,\n to_json(\n CASE WHEN typ IN ('script', 'flow') THEN clean - 'on_behalf_of' ELSE clean END\n - CASE WHEN typ = 'flow' THEN 'version_id'\n WHEN typ IN ('app', 'raw_app') THEN 'parent_version'\n ELSE '' END\n - CASE WHEN typ = 'script' AND NOT EXISTS (\n SELECT 1 FROM script sc WHERE sc.workspace_id::text = $2::text\n AND lpad(to_hex(sc.hash), 16, '0') = clean->>'parent_hash')\n THEN 'parent_hash' ELSE '' END\n ),\n created_at, email,\n CASE WHEN typ = 'script' AND EXISTS (\n SELECT 1 FROM script sc WHERE sc.workspace_id::text = $2::text\n AND lpad(to_hex(sc.hash), 16, '0') = base)\n THEN base END\n FROM (\n SELECT d.path, d.typ, d.created_at, d.email, d.base,\n replace(replace(replace(d.value::text, chr(92) || chr(92), chr(1)), chr(92) || 'u0000', ''), chr(1), chr(92) || chr(92))::jsonb AS clean\n FROM draft d\n WHERE d.workspace_id = $1\n AND CASE WHEN $4::text[] IS NULL THEN d.email = $3 OR d.email IS NULL\n ELSE d.email = $3\n AND (d.typ::text, d.path) IN (SELECT * FROM unnest($4::text[], $5::text[]))\n END\n ) s\n RETURNING typ AS \"typ: UserDraftItemKind\", path", + "query": "INSERT INTO draft (workspace_id, path, typ, value, created_at, email, base)\n SELECT $2::text, path, typ,\n to_json(\n CASE WHEN typ IN ('script', 'flow') THEN clean - 'on_behalf_of' ELSE clean END\n - CASE WHEN typ = 'flow' THEN 'version_id'\n WHEN typ IN ('app', 'raw_app') THEN 'parent_version'\n ELSE '' END\n - CASE WHEN typ <> 'script' THEN ''\n WHEN (clean->>'parent_hash' ~ '^[0-9a-f]{16}$') IS NOT TRUE THEN 'parent_hash'\n WHEN EXISTS (SELECT 1 FROM script sc WHERE sc.workspace_id = $2::text\n AND sc.hash = ('x' || (clean->>'parent_hash'))::bit(64)::bigint)\n THEN '' ELSE 'parent_hash' END\n ),\n created_at, email,\n CASE WHEN typ = 'script' AND base ~ '^[0-9a-f]{16}$' THEN\n CASE WHEN EXISTS (SELECT 1 FROM script sc WHERE sc.workspace_id = $2::text\n AND sc.hash = ('x' || base)::bit(64)::bigint)\n THEN base END\n END\n FROM (\n SELECT d.path, d.typ, d.created_at, d.email, d.base,\n replace(replace(replace(d.value::text, chr(92) || chr(92), chr(1)), chr(92) || 'u0000', ''), chr(1), chr(92) || chr(92))::jsonb AS clean\n FROM draft d\n WHERE d.workspace_id = $1\n AND CASE WHEN $4::text[] IS NULL THEN d.email = $3 OR d.email IS NULL\n ELSE d.email = $3\n AND (d.typ::text, d.path) IN (SELECT * FROM unnest($4::text[], $5::text[]))\n END\n ) s\n RETURNING typ AS \"typ: UserDraftItemKind\", path", "describe": { "columns": [ { @@ -62,5 +62,5 @@ false ] }, - "hash": "8b62d25c98a6b3db3a0b7039cacf79799e6d7c7c6c9532545844bec9ec314516" + "hash": "e9efda2d1ab6b111103cfcd626cd91e2241721e34586dedf7bdacb83b732f2b7" } diff --git a/backend/windmill-common/src/user_drafts.rs b/backend/windmill-common/src/user_drafts.rs index 61756d1683..b99c0a26d4 100644 --- a/backend/windmill-common/src/user_drafts.rs +++ b/backend/windmill-common/src/user_drafts.rs @@ -695,7 +695,8 @@ pub async fn copy_drafts_to_workspace( // deploy time, which is the more accurate answer of the two. let rows = sqlx::query!( // A script hash is content-addressed and shared between a workspace and its forks, so - // a script draft keeps its base (and `parent_hash`) when the target has that version, + // a script draft keeps its base (and `parent_hash`, both `ScriptHash`'s 16 hex digits, + // matched on the column so the lookup uses the key) when the target has that version, // which a fork's clone does; one deployed in the source after the fork was made is not // there, and a deploy naming it as parent would be refused. Flow and app ids are minted // per workspace, so those drafts arrive with no base (staleness falls back to the @@ -713,16 +714,18 @@ pub async fn copy_drafts_to_workspace( - CASE WHEN typ = 'flow' THEN 'version_id' WHEN typ IN ('app', 'raw_app') THEN 'parent_version' ELSE '' END - - CASE WHEN typ = 'script' AND NOT EXISTS ( - SELECT 1 FROM script sc WHERE sc.workspace_id::text = $2::text - AND lpad(to_hex(sc.hash), 16, '0') = clean->>'parent_hash') - THEN 'parent_hash' ELSE '' END + - CASE WHEN typ <> 'script' THEN '' + WHEN (clean->>'parent_hash' ~ '^[0-9a-f]{16}$') IS NOT TRUE THEN 'parent_hash' + WHEN EXISTS (SELECT 1 FROM script sc WHERE sc.workspace_id = $2::text + AND sc.hash = ('x' || (clean->>'parent_hash'))::bit(64)::bigint) + THEN '' ELSE 'parent_hash' END ), created_at, email, - CASE WHEN typ = 'script' AND EXISTS ( - SELECT 1 FROM script sc WHERE sc.workspace_id::text = $2::text - AND lpad(to_hex(sc.hash), 16, '0') = base) - THEN base END + CASE WHEN typ = 'script' AND base ~ '^[0-9a-f]{16}$' THEN + CASE WHEN EXISTS (SELECT 1 FROM script sc WHERE sc.workspace_id = $2::text + AND sc.hash = ('x' || base)::bit(64)::bigint) + THEN base END + END FROM ( SELECT d.path, d.typ, d.created_at, d.email, d.base, replace(replace(replace(d.value::text, chr(92) || chr(92), chr(1)), chr(92) || 'u0000', ''), chr(1), chr(92) || chr(92))::jsonb AS clean