@@ -92,11 +92,10 @@ return a result with the following criteria:
9292
9393# ## Configure routing rules with a file
9494
95- To express and fire routing rules, we use the
96- [easy-rules](https://github.com/j-easy/easy-rules) engine. These rules must be
97- stored in a YAML file. Rules consist of a name, description, condition, and list
95+ Rules consist of a name, description, condition, and list
9896of actions. If the condition of a particular rule evaluates to `true`, its
99- actions are fired.
97+ actions are fired. Rules are stored as a
98+ [multi-document](https://www.yaml.info/learn/document.html) YAML file.
10099
101100` ` ` yaml
102101---
@@ -113,20 +112,37 @@ actions:
113112 - 'result.put("routingGroup", "etl-special")'
114113` ` `
115114
116- In the condition, you can access the methods of a
117- [HttpServletRequest](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html)
118- object called `request`. Rules may also utilize
115+ Three objects are available by default. They are
116+ * `request`, the incoming request as an [HttpServletRequest](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html)
117+ * `state`, a `HashMap<String, Object>` that allows passing arbitrary state from one rule evaluation to the next
118+ * `result`, a `HashMap<String, String>` that is used to return the result of rule evaluation to the engine
119+
120+ In addition to the default objects, rules may optionally utilize
119121[trinoRequestUser](#trinorequestuser) and
120122[trinoQueryProperties](#trinoqueryproperties)
121- objects , which provide information about the user and query respectively.
123+ , which provide information about the user and query respectively.
122124You must include an action of the form `result.put(\"routingGroup\", \"foo\")`
123125to trigger routing of a request that satisfies the condition to the specific
124126routing group. Without this action, the default adhoc group is used and the
125127whole routing rule is redundant.
126128
127129The condition and actions are written in [MVEL](http://mvel.documentnode.com/),
128- an expression language with Java-like syntax. In most cases, you can write
129- conditions and actions in Java syntax and expect it to work. There are some
130+ an expression language with Java-like syntax. Classes from `java.util`, data-type
131+ classes from `java.lang` such as `Integer` and `String`, as well as `java.lang.Math`
132+ and `java.lang.StrictMath` are available in rules. Rules may not use `java.lang.System`
133+ and other classes that allow access the Java runtime and operating system.
134+ In most cases, you can write
135+ conditions and actions in Java syntax and expect it to work. One exception is
136+ parametrized types. Exclude type parameters, for example to add a `HashSet` to the
137+ `state` variable, use an action such as :
138+ ` ` ` java
139+ actions:
140+ - |
141+ state.put("triggeredRules",new HashSet())
142+ ` ` `
143+ This is equivalent to `new HashSet<Object>()`.
144+
145+ There are some
130146MVEL-specific operators. For example, instead of doing a null-check before
131147accessing the `String.contains` method like this :
132148
@@ -296,8 +312,8 @@ actions:
296312` ` `
297313
298314This can difficult to maintain with more rules. To have better control over the
299- execution of rules, we can use rule priorities and composite rules . Overall,
300- priorities, composite rules, and other constructs that MVEL support allows
315+ execution of rules, we can use rule priorities. Overall,
316+ priorities and other constructs that MVEL support allows
301317you to express your routing logic.
302318
303319# ### Rule priority
@@ -328,99 +344,52 @@ that the first rule (priority 0) is fired before the second rule (priority 1).
328344Thus `routingGroup` is set to `etl` and then to `etl-special`, so the
329345` routingGroup` is always `etl-special` in the end.
330346
331- More specific rules must be set to a lesser priority so they are evaluated last
332- to set a `routingGroup`. To further control the execution of rules, for example
333- to have only one rule fire, you can use composite rules.
347+ More specific rules must be set to a higher priority so they are evaluated last
348+ to set a `routingGroup`.
334349
335- # #### Composite rules
350+ # #### Passing State
336351
337- First, please refer to the [easy-rule composite rules documentation](https://github.com/j-easy/easy-rules/wiki/defining-rules#composite-rules).
338-
339- The preceding section covers how to control the order of rule execution using
340- priorities. In addition, you can configure evaluation so that only the first
341- rule matched fires (the highest priority one) and the rest is ignored. You can
342- use `ActivationRuleGroup` to achieve this :
352+ The `state` object may be used to pass information from one rule evaluation to
353+ the next. This allows an author to avoid duplicating logic in multiple rules.
354+ Priority should be used to ensure that `state` is updated before being used
355+ in downstream rules. For example, the atomic rules may be re-implemented as
343356
344357` ` ` yaml
345358---
346- name: "airflow rule group"
347- description: "routing rules for query from airflow"
348- compositeRuleType: "ActivationRuleGroup"
349- composingRules:
350- - name: "airflow special"
351- description: "if query from airflow with special label, route to etl-special group"
352- priority: 0
353- condition: 'request.getHeader("X-Trino-Source") == "airflow" && request.getHeader("X-Trino-Client-Tags") contains "label=special"'
354- actions:
355- - 'result.put("routingGroup", "etl-special")'
356- - name: "airflow"
357- description: "if query from airflow, route to etl group"
358- priority: 1
359- condition: 'request.getHeader("X-Trino-Source") == "airflow"'
360- actions:
361- - 'result.put("routingGroup", "etl")'
362- ` ` `
363-
364- Note that the priorities have switched. The more specific rule has a higher
365- priority, since it should fire first. A query coming from airflow with special
366- label is matched to the "airflow special" rule first, since it's higher
367- priority, and the second rule is ignored. A query coming from airflow with no
368- labels does not match the first rule, and is then tested and matched to the
369- second rule.
370-
371- You can also use `ConditionalRuleGroup` and `ActivationRuleGroup` to implement
372- an if/else workflow. The following logic in pseudocode :
373-
374- ` ` ` text
375- if source == "airflow":
376- if clientTags["label"] == "foo":
377- return "etl-foo"
378- else if clientTags["label"] = "bar":
379- return "etl-bar"
380- else
381- return "etl"
382- ` ` `
383-
384- This logic can be implemented with the following rules :
359+ name: "initialize state"
360+ description: "Add a set to the state map to track rules that have evaluated to true"
361+ priority: 0
362+ condition: "true"
363+ actions:
364+ - |
365+ state.put("triggeredRules",new HashSet())
366+ # MVEL does not support type parameters! HashSet<String>() would result in an error.
367+ ---
368+ name: "airflow"
369+ description: "if query from airflow, route to etl group"
370+ priority: 1
371+ condition: |
372+ request.getHeader("X-Trino-Source") == "airflow"
373+ actions:
374+ - |
375+ result.put("routingGroup", "etl")
376+ - |
377+ state.get("triggeredRules").add("airflow")
378+ ---
379+ name: "airflow special"
380+ description: "if query from airflow with special label, route to etl-special group"
381+ priority: 2
382+ condition: |
383+ state.get("triggeredRules").contains("airflow") && request.getHeader("X-Trino-Client-Tags") contains "label=special"
384+ actions:
385+ - |
386+ result.put("routingGroup", "etl-special")
385387
386- ` ` ` yaml
387- name: "airflow rule group"
388- description: "routing rules for query from airflow"
389- compositeRuleType: "ConditionalRuleGroup"
390- composingRules:
391- - name: "main condition"
392- description: "source is airflow"
393- priority: 0 # rule with the highest priority acts as main condition
394- condition: 'request.getHeader("X-Trino-Source") == "airflow"'
395- actions:
396- - ""
397- - name: "airflow subrules"
398- compositeRuleType: "ActivationRuleGroup" # use ActivationRuleGroup to simulate if/else
399- composingRules:
400- - name: "label foo"
401- description: "label client tag is foo"
402- priority: 0
403- condition: 'request.getHeader("X-Trino-Client-Tags") contains "label=foo"'
404- actions:
405- - 'result.put("routingGroup", "etl-foo")'
406- - name: "label bar"
407- description: "label client tag is bar"
408- priority: 0
409- condition: 'request.getHeader("X-Trino-Client-Tags") contains "label=bar"'
410- actions:
411- - 'result.put("routingGroup", "etl-bar")'
412- - name: "airflow default"
413- description: "airflow queries default to etl"
414- condition: "true"
415- actions:
416- - 'result.put("routingGroup", "etl")'
417388` ` `
418389
419390# #### If statements (MVEL Flow Control)
420391
421- In the preceding section you see how `ConditionalRuleGroup` and
422- ` ActivationRuleGroup` are used to implement an `if/else` workflow. You can
423- use MVEL support for `if` statements and other flow control. The following logic
392+ You can use MVEL support for `if` statements and other flow control. The following logic
424393in pseudocode :
425394
426395` ` ` text
0 commit comments