I don't think the planner is the right place; in general planner 
transformations preserve semantics, whereas your transformation won't 
necessarily. Planner rules are also designed for optimization, and here there 
is nothing to optimize.

The visitor-based APIs are probably the main tool you need.
https://www.feldera.com/blog/calcite-irs#9099c1483ca6

Mihai

________________________________
From: Kristian Lein-Mathisen <kristianl...@gmail.com>
Sent: Monday, March 17, 2025 7:57 AM
To: dev@calcite.apache.org <dev@calcite.apache.org>
Subject: Parsing expression inside RelShuttle

Hi!

I'm new to Calcite and I'd like to use it to transform incoming SQL queries
to conform to some generic policy rules, like wrapping all table-scans with
a row-level filter. I hope this is the right place to ask questions like
this. Here's an example of what I'm trying to acheive:

  -- admin says: ALTER TABLE orders SET TBLPROPERTIES (policy.filter =
'country IN user_countries')
  SELECT * FROM orders -- <-- from user
  SELECT * FROM (SELECT * FROM orders WHERE country IN user_countries()) --
<-- to backend

This is fairly simple to acheive when you have a static filter expression.
Here's what I've got working:

    public static RelNode policyFilter(RelNode root, RelBuilder builder) {
        return root.accept(new RelShuttleImpl() {
            @Override
            public RelNode visit(TableScan scan) {
                RelNode n = super.visit(scan);
                RexNode x = builder.in(builder.field("country"),
                        builder.literal("se"),
                        builder.literal("nl"),
                        builder.literal("dk"));
                return builder
                        .push(n)
                        .filter(x)
                        .build();
            }
        });
    }

This works, provided all my tables have a "country" column. What I'd like
to do, however, is allow user-defined filter expressions: make "RexNode x"
from a per-table String. I'd like this to be evaluated in the context of
the current table (so you can reference column names) and session (so
current_user(), for example, is available). I've looked at the Planner, but
it doesn't seem it's exposing enough of its internals (I don't have access
to its validator.validateExpression, for example).

Note that I'm new to Calcite, so I could be going around this completely
wrong. Looking at other uses of Calcite, I'm getting the impression that I
should be extending RelNodes and adding some Rules. I've also assumed that
I should be doing this with RelNodes instead of SqlNodes.

For the record, Kyuubi does something very similar using Spark here:
https://github.com/apache/kyuubi/blob/master/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/rule/rowfilter/RuleApplyRowFilter.scala#L42

Thanks for your feedback,
- Kris

Reply via email to