I would like to find a way to transform data structure in Prolog given set of constraints/transformation rules.
Motivating example
Let's say we want to validate/enrich SQL queries according to some rules.
We are only interested in simple SQL queries and only consider WHERE/GROUP BY clauses now, of the form WHERE filter(a) AND filter(b) AND ... GROUP BY c,d,... (where
a, b, c, d are column names).
The model of such query could look like:
[filter(a), filter(b), group(c), group(d)]
We may have rules like this:
- Column
amust be present in either filter or grouping (but only once). If not present, generate 2 solutions by adding to filter and to grouping. - Grouping must not be empty (if empty, add default grouping by column
a). - Must be not more than 2 grouping (if more than 2 then generate multiple solutions by removing extra grouping).
- No column may be present in both filter and grouping (if it happens then generate 2 solutions by removing column from either filter and grouping).
- etc.
Some rules are obviously "conflicting" (e.g. if we add mandatory grouping we may exceed max number of groupings and will have to produce multiple solutions or no solutions at all, depending on specific rules).
What I tried
So far I was only able to come up with something like this:
transform(In, Out) :-
has_mandatory_grouping(In, Out1),
limited_grouping_count(Out1, Out2),
another_rule(Out2, Out3),
...
last_rule(OutN, Out).
has_mandatory_grouping(In, Out) :- contains(In, group(a)) -> Out = In; Out = [group(a)|In].
limited_grouping_count(In, Out) :- (length(In, L), L > 2) -> subset2(Out, In), Out = [_,_], contains(Out, group(a)); Out = In.
...
So basically an ordered set of steps, each step applying some logic to produce new structure to be passed downstream.
What I don't like about such approach
- It's hard to enforce constraints in a composable way (unless carefully coded, later steps may change the structure so that rules applied earlier are violated). Maybe adding pruning steps at the end could help but then it looks like a duplication of logic.
- Looks like I don't actually use power of the LP language - I basically doing what can be (more easily?) done in any average language (minus static typing, plus symbols support).
- Actual task will be a bit more complicated (e.g. I may need to check filter expression satisfiability), so I would like to find a scalable and maintainable solution.
Other ideas
I also considered CHR but due to its committed-choice nature I can't see how it can be used in the task which requires solution branching (probing - generating and pruning - alternative solutions).
It's probably possible to support branching by simulating presence of the multiple constraint stores (by attaching special "branch" argument to all constraints/terms), but that's definitely over my abilities.
Question
Is there a way to model these rules in Prolog so that the code is more expressive than can be done in the non-LP language?
Or should I consider other systems (starting with CLP)?
Update on CHR
Solution based on CHR and backtracking (suggested by @Isabelle Newbie) works, but resulting rules may still lack composability (e.g. post([group(c), group(d)]) does not terminate because different rules keep triggering each other).
While it can be solved ad-hoc by carefully coding and testing CHR, maybe some higher-level technique could help? Like if we could put constraints:
|groupings| =< 2
(a ∈ groupings) OR (a ∈ filters)
max(|groupings ⋂ original_groupings|)
and make machine find variants.
I guess it's possible in principle, but could it be a feasible approach for developing production system? Maybe I should create a separate question for this.