Kevin
Kevin Author of Omnia Blog.

Why Your AWS IoT Rule Isn't Firing: IN ... AND in the WHERE Clause Returns Undefined

Why Your AWS IoT Rule Isn't Firing: IN ... AND in the WHERE Clause Returns Undefined

I came across a strange issue a few weeks ago when working with AWS IoT Core. I had a topic rule that deployed without any errors and showed as enabled in the console, but it simply never fired. No error, nothing in the logs, no data downstream.

Here’s the rule:

SELECT *
FROM 'sensors/+/v1/+/+'
WHERE topic(4) IN ['mobile', 'web']
  AND topic(5) IN ['orderCreated', 'orderUpdated']

It turns out that an IN expression followed by AND makes the whole WHERE clause evaluate to Undefined, and the message gets silently dropped. The fix is just a pair of parentheses. Here’s to share how I found it, why it happens, and how to spot it in the logs.


The Problem

Messages published to sensors/42/v1/mobile/orderCreated matched the topic filter, but the rule action never ran.

At first glance, you might think this is a wildcard issue. The FROM clause ends with +, the WHERE clause filters on that exact position, and a similar rule with a literal last segment worked fine. That was my first guess too. However, it turned out to be wrong. So what was actually going on?


Finding the Real Error

By default, a rule that silently drops messages gives you nothing to go on. So the first step is to turn on DEBUG logging for AWS IoT Core:

aws iot set-v2-logging-options \
  --role-arn arn:aws:iam::YOUR_ACCOUNT_ID:role/YOUR_IOT_LOGGING_ROLE \
  --default-log-level DEBUG

After publishing a test message, I checked the AWSIotLogsV2 log group and found this:

RuleMatch      INFO   Success
RuleExecution  DEBUG  Success   "Message does not satisfy WHERE clause condition"
RuleExecution  ERROR  Failure   ruleAction=EvaluatingWhereClause  "Undefined result"

The last line is the important one. The WHERE clause didn’t evaluate to false. It evaluated to Undefined, and the message was dropped.

Note: Don’t forget to turn DEBUG logging off when you’re done (--default-log-level DISABLED or your usual level). It’s noisy, and you pay for the log volume.


Narrowing It Down

The tricky part was that my “working” and “broken” rules differed in two ways at the same time:

  • The last FROM segment was a literal in one and a + in the other.
  • The WHERE clause had one condition in one and two conditions joined with AND in the other.

So I created a bunch of rules that each changed only one thing, all on SQL version 2016-03-23, and published the same message to each of them:

WHERE clause Result
topic(5) IN ['orderCreated', 'orderUpdated'] Fires
topic(5) = 'orderCreated' Fires
topic(4) = 'mobile' AND topic(5) = 'orderCreated' Fires
topic(4) IN [...] OR topic(5) IN [...] Fires
topic(5) = 'orderCreated' AND topic(4) IN [...] Fires
topic(4) IN [...] AND topic(5) = 'orderCreated' Undefined
topic(4) IN [...] AND topic(5) IN [...] Undefined
(topic(4) IN [...]) AND (topic(5) IN [...]) Fires

Wildcards weren’t the problem at all. topic(N) resolved correctly with trailing +, consecutive +, multi-level #, and short filters like a/+.

The pattern is clear: an IN expression without parentheses, followed by AND.


Minimal Reproduction

Once I knew what to look for, I was able to strip away everything else. No topic(), no wildcards, no payload:

-- Undefined, message dropped
SELECT * FROM 'a/b' WHERE 'mobile' IN ['mobile', 'web'] AND 1 = 1

-- Fires
SELECT * FROM 'a/b' WHERE 1 = 1 AND 'mobile' IN ['mobile', 'web']

-- Fires
SELECT * FROM 'a/b' WHERE ('mobile' IN ['mobile', 'web']) AND 1 = 1

-- Fires
SELECT * FROM 'a/b' WHERE 'mobile' IN ['mobile', 'web']

A few more things I found along the way:

  • It’s positional. IN before AND fails, but IN as the last term works.
  • OR is not affected. Only AND triggers it.
  • It’s not about array literals. Using an array from the payload (WHERE topic(4) IN allowed AND ...) fails the same way.
  • The data doesn’t matter. It returns Undefined whether the conditions should be true or false.
  • The rule action doesn’t matter. Republish, CloudWatch Logs, and Lambda actions all behave the same.

Why Does This Happen?

I can’t see how the IoT SQL parser works internally, and I couldn’t find this documented anywhere. So this part is my best guess. Let’s break it down step by step:

1. IN Seems to Bind More Loosely Than AND

This expression:

X IN [list] AND Y

appears to be parsed as:

X IN ([list] AND Y)

2. AND With Non-Boolean Operands Returns Undefined

According to the AWS IoT SQL operators reference, AND returns Undefined when its operands aren’t Boolean. So [list] AND Y becomes Undefined.

3. Undefined Spreads to the Whole Clause

X IN Undefined is also Undefined, so the entire WHERE clause is Undefined and the message is dropped.

This explains everything I observed. When IN is the last term, there’s nothing after it to get pulled in. Parentheses force the intended grouping. And since the failure happens before any values are compared, the data never matters.


Fixing the Rule

To solve this, wrap each IN condition in parentheses:

SELECT *
FROM 'sensors/+/v1/+/+'
WHERE (topic(4) IN ['mobile', 'web'])
  AND (topic(5) IN ['orderCreated', 'orderUpdated'])

Key Changes:

  1. Wrap topic(4) IN [...] in parentheses.
  2. Wrap topic(5) IN [...] in parentheses.

That’s it. The trailing wildcards stay, and you don’t need to split the rule into multiple rules.

Technically, only the IN that comes before an AND needs parentheses. But I’d still recommend wrapping every IN, so the rule stays correct if you reorder or add conditions later.


Reading the Logs: False vs. Undefined vs. Action Failure

The most useful thing I learned from this was how to tell different failures apart in the DEBUG logs.

1. Rule Fired Normally

RuleMatch              INFO   Success
StartingRuleExecution  DEBUG  Success   ruleAction=LambdaAction
RuleExecution          INFO   Success   ruleAction=LambdaAction

2. WHERE Clause Was Actually False

RuleMatch              INFO   Success
RuleExecution          DEBUG  Success   "Message does not satisfy WHERE clause condition"

3. WHERE Clause Couldn’t Be Evaluated (This Issue)

RuleMatch              INFO   Success
RuleExecution          DEBUG  Success   "Message does not satisfy WHERE clause condition"
RuleExecution          ERROR  Failure   ruleAction=EvaluatingWhereClause  "Undefined result"

4. WHERE Clause Passed, but the Action Failed

For example, a missing Lambda invoke permission:

RuleMatch              INFO   Success
StartingRuleExecution  DEBUG  Success   ruleAction=LambdaAction
RuleExecution          ERROR  Failure   ruleAction=LambdaAction
   "iot.amazonaws.com is unable to perform: lambda:InvokeFunction on resource: ..."

Two things tell them apart:

  • StartingRuleExecution: if it’s there, the WHERE clause passed and the action was attempted. If it’s missing, look at your WHERE clause.
  • ruleAction on the ERROR entry: it tells you which stage failed, either EvaluatingWhereClause or the action itself.

One related gotcha: Lambda invoke permissions for IoT rules are granted per rule, using the rule ARN as the SourceArn. AWS SAM’s IoTRule event creates this permission for you, but a rule you create by hand for testing won’t have one. You can check with:

aws lambda get-policy --function-name YOUR_FUNCTION

Key Takeaways

  1. Is your IoT rule silently not firing?
    • Turn on DEBUG logging and look for EvaluatingWhereClause with "Undefined result".
  2. X IN [...] AND Y evaluates to Undefined:
    • This happens when the IN isn’t wrapped in parentheses and is followed by AND. OR isn’t affected.
  3. Wrap every IN in parentheses:
    • (X IN [...]) AND Y works as expected.
  4. topic() works fine with wildcards:
    • Trailing, consecutive, and multi-level wildcards all resolved correctly in both SELECT and WHERE.
  5. Change one thing at a time when debugging:
    • The obvious suspect here (trailing wildcards) turned out to be innocent.

Final Thoughts

A rule that gets accepted, shows as enabled, and quietly does nothing is one of the worst kinds of bugs, because nothing tells you it’s broken until data goes missing.

A few caveats: I only tested SQL version 2016-03-23 in a single region. I also haven’t checked whether the ERROR entry shows up at log levels below DEBUG, or whether any CloudWatch metric counts this failure. AWS might change this behavior in the future, so it’s worth re-running the minimal repro above if you’re reading this later.

I hope this saves someone a few hours of debugging. Let me know in the comments if you run into the same issue or find anything different! :)

comments powered by Disqus