Repository: edgedb/edgedb

Issue:
Access policy dislikes omitted fields
- EdgeDB Version: 2.0+88c1706, 2.1+52c90a7
- Schema:

    ```
    type A {
        access policy soft_delete
            allow select, update read, insert using (NOT (EXISTS (.ts)));
        access policy update_write
            allow update write ;
        property ts -> std::datetime;
    };
    ```

Steps to Reproduce:

1. Insert with `ts` field works:

    ```
    edgedb> insert A { ts := {} };
    {default::A {id: ccd0f2da-1509-11ed-af6c-ef6982b67324}}
    ```

2. Insert without `ts` field doesn't:

   ```
   edgedb> insert A;
   edgedb error: InternalServerError: there is no range var for (default::A) source in <pg.SelectStmt at 0x1157ece20>
     Hint: This is most likely a bug in EdgeDB. Please consider opening an issue ticket at https://github.com/edgedb/edgedb/issues/new?template=bug_report.md
     Server traceback:
         Traceback (most recent call last):
           File "edb/pgsql/compiler/__init__.py", line 120, in compile_ir_to_sql_tree
             qtree = dispatch.compile(ir_expr, ctx=ctx)
           File "python3.10/functools.py", line 889, in wrapper
             return dispatch(args[0].__class__)(*args, **kw)
           File "edb/pgsql/compiler/expr.py", line 60, in compile_Set
             _compile_set_impl(ir_set, ctx=ctx)
           File "edb/pgsql/compiler/expr.py", line 114, in _compile_set_impl
             _compile_set(ir_set, ctx=ctx)
           File "edb/pgsql/compiler/expr.py", line 633, in _compile_set
             relgen.get_set_rvar(ir_set, ctx=ctx)
           File "edb/pgsql/compiler/relgen.py", line 160, in get_set_rvar
             return _process_toplevel_query(ir_set, ctx=ctx)
           File "edb/pgsql/compiler/relgen.py", line 263, in _process_toplevel_query
             rvars = _get_set_rvar(ir_set, ctx=ctx)
           File "edb/pgsql/compiler/relgen.py", line 302, in _get_set_rvar
             rvars = process_set_as_subquery(ir_set, stmt, ctx=ctx)
           File "edb/pgsql/compiler/relgen.py", line 1349, in process_set_as_subquery
             dispatch.visit(expr, ctx=newctx)
           File "python3.10/functools.py", line 889, in wrapper
             return dispatch(args[0].__class__)(*args, **kw)
           File "edb/pgsql/compiler/dispatch.py", line 44, in visit
             compile(ir, ctx=ctx)
           File "python3.10/functools.py", line 889, in wrapper
             return dispatch(args[0].__class__)(*args, **kw)
           File "edb/pgsql/compiler/stmt.py", line 165, in compile_InsertStmt
             dml.process_insert_body(
           File "edb/pgsql/compiler/dml.py", line 796, in process_insert_body
             policy_cte = compile_policy_check(
           File "edb/pgsql/compiler/dml.py", line 842, in compile_policy_check
             cond_ref = clauses.compile_filter_clause(
           File "edb/pgsql/compiler/clauses.py", line 240, in compile_filter_clause
             dispatch.visit(ir_set, ctx=subctx)
           File "python3.10/functools.py", line 889, in wrapper
             return dispatch(args[0].__class__)(*args, **kw)
           File "edb/pgsql/compiler/expr.py", line 85, in visit_Set
             _compile_set_impl(ir_set, ctx=ctx)
           File "edb/pgsql/compiler/expr.py", line 114, in _compile_set_impl
             _compile_set(ir_set, ctx=ctx)
           File "edb/pgsql/compiler/expr.py", line 633, in _compile_set
             relgen.get_set_rvar(ir_set, ctx=ctx)
           File "edb/pgsql/compiler/relgen.py", line 207, in get_set_rvar
             rvars = _get_set_rvar(ir_set, ctx=subctx)
           File "edb/pgsql/compiler/relgen.py", line 412, in _get_set_rvar
             rvars = process_set_as_oper_expr(ir_set, stmt, ctx=ctx)
           File "edb/pgsql/compiler/relgen.py", line 2117, in process_set_as_oper_expr
             args = _compile_call_args(ir_set, ctx=newctx)
           File "edb/pgsql/compiler/relgen.py", line 3001, in _compile_call_args
             arg_ref = dispatch.compile(ir_arg.expr, ctx=ctx)
           File "python3.10/functools.py", line 889, in wrapper
             return dispatch(args[0].__class__)(*args, **kw)
           File "edb/pgsql/compiler/expr.py", line 60, in compile_Set
             _compile_set_impl(ir_set, ctx=ctx)
           File "edb/pgsql/compiler/expr.py", line 114, in _compile_set_impl
             _compile_set(ir_set, ctx=ctx)
           File "edb/pgsql/compiler/expr.py", line 633, in _compile_set
             relgen.get_set_rvar(ir_set, ctx=ctx)
           File "edb/pgsql/compiler/relgen.py", line 207, in get_set_rvar
             rvars = _get_set_rvar(ir_set, ctx=subctx)
           File "edb/pgsql/compiler/relgen.py", line 393, in _get_set_rvar
             rvars = process_set_as_exists_expr(ir_set, stmt, ctx=ctx)
           File "edb/pgsql/compiler/relgen.py", line 3410, in process_set_as_exists_expr
             set_ref = dispatch.compile(ir_expr, ctx=subctx)
           File "python3.10/functools.py", line 889, in wrapper
             return dispatch(args[0].__class__)(*args, **kw)
           File "edb/pgsql/compiler/expr.py", line 60, in compile_Set
             _compile_set_impl(ir_set, ctx=ctx)
           File "edb/pgsql/compiler/expr.py", line 110, in _compile_set_impl
             _compile_set(ir_set, ctx=scopectx)
           File "edb/pgsql/compiler/expr.py", line 633, in _compile_set
             relgen.get_set_rvar(ir_set, ctx=ctx)
           File "edb/pgsql/compiler/relgen.py", line 207, in get_set_rvar
             rvars = _get_set_rvar(ir_set, ctx=subctx)
           File "edb/pgsql/compiler/relgen.py", line 302, in _get_set_rvar
             rvars = process_set_as_subquery(ir_set, stmt, ctx=ctx)
           File "edb/pgsql/compiler/relgen.py", line 1349, in process_set_as_subquery
             dispatch.visit(expr, ctx=newctx)
           File "python3.10/functools.py", line 889, in wrapper
             return dispatch(args[0].__class__)(*args, **kw)
           File "edb/pgsql/compiler/dispatch.py", line 44, in visit
             compile(ir, ctx=ctx)
           File "python3.10/functools.py", line 889, in wrapper
             return dispatch(args[0].__class__)(*args, **kw)
           File "edb/pgsql/compiler/stmt.py", line 97, in compile_SelectStmt
             outvar = clauses.compile_output(stmt.result, ctx=ictx)
           File "edb/pgsql/compiler/clauses.py", line 219, in compile_output
             val = pathctx.get_path_value_output(
           File "edb/pgsql/compiler/pathctx.py", line 1273, in get_path_value_output
             return get_path_output(rel, path_id, aspect='value', env=env)
           File "edb/pgsql/compiler/pathctx.py", line 1079, in get_path_output
             return _get_path_output(rel, path_id=path_id, aspect=aspect,
           File "edb/pgsql/compiler/pathctx.py", line 1139, in _get_path_output
             ref = get_path_var(rel, path_id, aspect=aspect, flavor=flavor, env=env)
           File "edb/pgsql/compiler/pathctx.py", line 290, in get_path_var
             outvar = get_path_output(
           File "edb/pgsql/compiler/pathctx.py", line 1079, in get_path_output
             return _get_path_output(rel, path_id=path_id, aspect=aspect,
           File "edb/pgsql/compiler/pathctx.py", line 1139, in _get_path_output
             ref = get_path_var(rel, path_id, aspect=aspect, flavor=flavor, env=env)
           File "edb/pgsql/compiler/pathctx.py", line 260, in get_path_var
             raise LookupError(
         LookupError: there is no range var for (default::A) source in <pg.SelectStmt at 0x1157ece20>
    
         The above exception was the direct cause of the following exception:
    
         Traceback (most recent call last):
           File "edb/server/compiler_pool/worker_proc.py", line 55, in worker
             res = meth(*args)
           File "edb/server/compiler_pool/worker.py", line 160, in compile
             units, cstate = COMPILER.compile(
           File "edb/server/compiler/compiler.py", line 2207, in compile
             unit_group = self._compile(ctx=ctx, source=source)
           File "edb/server/compiler/compiler.py", line 1738, in _compile
             return self._try_compile(ctx=ctx, source=source)
           File "edb/server/compiler/compiler.py", line 1802, in _try_compile
             comp, capabilities = self._compile_dispatch_ql(
           File "edb/server/compiler/compiler.py", line 1712, in _compile_dispatch_ql
             query = self._compile_ql_query(ctx, ql, script_info=script_info)
           File "edb/server/compiler/compiler.py", line 637, in _compile_ql_query
             sql_text, argmap = pg_compiler.compile_ir_to_sql(
           File "edb/pgsql/compiler/__init__.py", line 152, in compile_ir_to_sql
             qtree = compile_ir_to_sql_tree(
           File "edb/pgsql/compiler/__init__.py", line 135, in compile_ir_to_sql_tree
             raise errors.InternalServerError(*args) from e
         edb.errors.InternalServerError: there is no range var for (default::A) source in <pg.SelectStmt at 0x1157ece20>
   ```


Discussion:

Resolving patch:
diff --git a/edb/pgsql/compiler/dml.py b/edb/pgsql/compiler/dml.py
--- a/edb/pgsql/compiler/dml.py
+++ b/edb/pgsql/compiler/dml.py
@@ -627,7 +627,10 @@ def dynamic_get_path(
             return None
         if not (rptr := path_id.rptr()):
             return None
-        return ptr_map.get(rptr.real_material_ptr)
+        if ret := ptr_map.get(rptr.real_material_ptr):
+            return ret
+        # Properties that aren't specified are {}
+        return pgast.NullConstant()
 
     fallback_rvar = pgast.DynamicRangeVar(dynamic_get_path=dynamic_get_path)
     pathctx.put_path_source_rvar(


Test patch:
diff --git a/tests/test_edgeql_policies.py b/tests/test_edgeql_policies.py
--- a/tests/test_edgeql_policies.py
+++ b/tests/test_edgeql_policies.py
@@ -733,3 +733,35 @@ async def test_edgeql_policies_cycle_04(self):
                     create access policy z allow all using (exists C);
                 };
             ''')
+
+    async def test_edgeql_policies_missing_prop_01(self):
+        await self.con.execute('''
+            CREATE TYPE A {
+                CREATE PROPERTY ts -> datetime;
+                CREATE ACCESS POLICY soft_delete
+                    ALLOW SELECT, UPDATE READ, INSERT
+                    USING (NOT (EXISTS (.ts)));
+                CREATE ACCESS POLICY update_write
+                    ALLOW UPDATE WRITE;
+            }
+        ''')
+
+        await self.con.execute('''
+            insert A;
+        ''')
+
+    async def test_edgeql_policies_missing_prop_02(self):
+        await self.con.execute('''
+            CREATE TYPE A {
+                CREATE MULTI PROPERTY ts -> datetime;
+                CREATE ACCESS POLICY soft_delete
+                    ALLOW SELECT, UPDATE READ, INSERT
+                    USING (NOT (EXISTS (.ts)));
+                CREATE ACCESS POLICY update_write
+                    ALLOW UPDATE WRITE;
+            }
+        ''')
+
+        await self.con.execute('''
+            insert A;
+        ''')