Repository: edgedb/edgedb

Issue:
Duplicate INSERTS when using CTE
- EdgeDB Version: EdgeDB 1.0-beta.3+g8ab4a7be0.cv202108020000 (repl 1.0.0-beta.3)
- OS Version: Ubuntu 20.4

Steps to Reproduce:

1. Run these EdgeQL statements

    ```sql
    START MIGRATION TO {
        module default {
            scalar type OrderNo extending sequence;
            type User {
                required property name -> str {
                    constraint exclusive;
                }
            }
            type Product {
                required property name -> str;
                required link user -> User;
                constraint exclusive on ( (.user, .name) );
            }
            type SaleOrder {
                required property order_num -> OrderNo;
                multi link lines -> OrderLine;
                required link customer -> User;
                required link merchant -> User;
                property create_date -> datetime {
                    default := datetime_current();
                }
            }
            type OrderLine {
                required link product -> Product;
                required property qty -> int32;
            }
            type SMS {
                required property body -> str;
                required link so -> SaleOrder;
            }
        }
    };
    POPULATE MIGRATION;
    COMMIT MIGRATION;

    INSERT User {name := "Alice"};
    INSERT User {name := "Bob"};
    INSERT Product {name := "Product 1", user := (SELECT User FILTER .name = "Alice")};
    INSERT Product {name := "Product 1", user := (SELECT User FILTER .name = "Bob")};
    
    # Insert SMS and SaleOrder in a single query (https://github.com/edgedb/edgedb/discussions/2750)
    WITH
        NewOrder := (
            INSERT SaleOrder {
            customer := (SELECT User FILTER .name = <str>"Alice"), 
            merchant := (SELECT User FILTER .name = <str>"Bob"), 
            lines := {(
                INSERT OrderLine { 
                    product := (SELECT Product FILTER .name = <str>"Product 1" AND .user.name = <str>"Bob"), 
                    qty := <int32>1
                    }
                )}
        }
    )
    SELECT (
        INSERT SMS {
            body := "Your order #" ++ <str>NewOrder.order_num ++ "was placed successfully",
            so := NewOrder,
        }
    ) {
        body,
        so : {
            customer,
            merchant,
            lines : {
                product,
                qty
            }
        }
    };
    ```
2. This creates duplicate links at `so.line`.
    
    ```
        {
    default::SMS {
        body: 'Your order #1was placed successfully',
        so: default::SaleOrder {
        customer: default::User {id: c607024a-1083-11ec-acae-af746e748347},
        merchant: default::User {id: c608bdba-1083-11ec-acae-dfb9e5e30284},
        lines: {
            default::OrderLine {product: default::Product {id: c619b62e-1083-11ec-acae-53a9bd4857a5}, qty: 1},
            default::OrderLine {product: default::Product {id: c619b62e-1083-11ec-acae-53a9bd4857a5}, qty: 1},
        },
        },
    },
    }

    ```
    
3.  But if I try creating an order directly, intead of a CTE I don't get duplicates
    
    ```
    SELECT (
        INSERT SaleOrder {
            customer := (SELECT User FILTER .name = <str>"Alice"), 
            merchant := (SELECT User FILTER .name = <str>"Bob"), 
            lines := {
                (
                    INSERT OrderLine { 
                        product := (SELECT Product FILTER .name = <str>"Product 1" AND .user.name = <str>"Bob"), 
                        qty := <int32>1
                    }
                )
            }
        }
    ) {
        lines : {id, product}
    }
    ```
    Which results in no duplicates
    ```
    {
        default::SaleOrder {
            lines: {
                default::OrderLine {id: 7245802c-1084-11ec-9ebb-b38ae45012bc, product: default::Product {id: c619b62e-1083-11ec-acae-53a9bd4857a5}}
            }
        }
    }
    ```


Looks like @msullivan is already working on this at https://github.com/edgedb/edgedb/issues/2809


Discussion:
Seems like a DML materialization bug to me.  `NewOrder` is evaluated twice (equal to the number of references in main DML body).
This can be reproduced with this query, on our `insert` test repo:
```
WITH                                                                      
    New := (                                                              
        INSERT Person {                                                   
            name := "test",                                               
            notes := (INSERT Note { name := "test" })                     
         }                                                                
    ),                                                                    
SELECT (                                                                  
    INSERT Person2a {                                                     
        first := New.name, last := "!", bff := New,                       
    }                                                                     
) {                                                                       
    first,                                                                
    bff: {                                                                
        name,                                                             
        notes: { id, name }                                               
    }                                                                     
};
```
Elvis's analysis about the number of references is right; if we reference it a third time, we get three entries back.
But curiously, the DML is not actually being executed multiple times: all three orders (or notes) coming back are the same object, and only one insert happens.

Resolving patch:
diff --git a/edb/pgsql/compiler/relctx.py b/edb/pgsql/compiler/relctx.py
--- a/edb/pgsql/compiler/relctx.py
+++ b/edb/pgsql/compiler/relctx.py
@@ -1795,13 +1795,16 @@ def _add_ptr_rel_overlay(
         path_id: irast.PathId,
         ctx: context.CompilerContextLevel) -> None:
 
+    entry = (op, rel, path_id)
     if dml_stmts:
         for dml_stmt in dml_stmts:
             overlays = ctx.ptr_rel_overlays[dml_stmt][typeid, ptrref_name]
-            overlays.append((op, rel, path_id))
+            if entry not in overlays:
+                overlays.append(entry)
     else:
         overlays = ctx.ptr_rel_overlays[None][typeid, ptrref_name]
-        overlays.append((op, rel, path_id))
+        if entry not in overlays:
+            overlays.append(entry)
 
 
 def add_ptr_rel_overlay(


Test patch:
diff --git a/tests/test_edgeql_insert.py b/tests/test_edgeql_insert.py
--- a/tests/test_edgeql_insert.py
+++ b/tests/test_edgeql_insert.py
@@ -4817,3 +4817,37 @@ async def test_edgeql_insert_enumerate_01(self):
                 [0, {}, {}],
             ]
         )
+
+    async def test_edgeql_insert_nested_and_with_01(self):
+        await self.assert_query_result(
+            r"""
+                WITH
+                    New := (
+                        INSERT Person {
+                            name := "test",
+                            notes := (INSERT Note { name := "test" })
+                         }
+                    ),
+                SELECT (
+                    INSERT Person2a {
+                        first := New.name, last := "!", bff := New,
+                    }
+                ) {
+                    first,
+                    bff: {
+                        name,
+                        notes: { name }
+                    }
+                };
+            """,
+            [
+                {
+                    "first": "test",
+                    "bff": {
+                        "name": "test",
+                        "notes": [{"name": "test"}]
+                    },
+                }
+            ]
+
+        )