Best practices for handling array rule inputs that aren't true null when passed a singleton of the same type, especially in regards to query entity rules?

Certified Senior Developer

Hi all,

 

Wanted to ask the community to see if anyone has some best practice recommendations to handle the edge cases caused by Appian limitations in how it detects null/empty values and how it converts null singleton types into array rule inputs; and especially recommendations on setting up re-usable query entity rules.

 

We've discovered that Appian handles nulls in unintuitive ways depending on the data-type. If a rule input is an array, and you pass in a singleton of the same type that is null, Appian will automatically convert that input to {null} (a one element array where that element is null), rather than null or {} (empty). According to Appian support, this is the expected behavior of the product.

 

However, this causes two issues for us:

 

 

  1. The out-of-the-box isnull() and a!isNullOrEmpty() functions return false, despite a reasonable assumption that the value is null/empty. (It is not. It is a one element array with that element being null, due to how the product casts singletons to arrays.)

 

A work-around here is to instead use a common rule that includes a length(ri!input)=0 check, as in: or(a!isNullOrEmpty(ri!input), length(ri!input) = 0 ). The downside here is that it is very easy for developers to miss using this.

 

  1. If the rule input is used in an a!queryEntity() filter, "ignoreFiltersWithEmptyValues" will not ignore this filter, despite a null being passed into the rule input (again because it is automatically converted to something that is not null or empty).

 

We frequently develop expression rules for re-usable queries and prefer to use array inputs so that we can optionally use an "IN" filter, even in cases where we usually only pass in a single value to that filter. However, this is causing failures when we pass a null value in. We've found a few possible work-arounds, but there are drawbacks to each:

 

  1. When passing the single input that might be null, wrap it in a!defaultValue(ri!input, null). If it's null, it will replace it with a typeless null, which correctly gets converted to null of the array type and works as expected. The downside of this is that it places the burden on the rule-caller to ensure this is done, and this is easily missed (and it makes calling the rule non-intuitive).
  2. Update our a!queryEntity rules to add applyWhen: not(length(ri!filterValue)=0), since we can't rely on "ignoreFiltersWithEmptyValues" to catch this edge case. The downside here is that we've already constructed over a thousand query entity rules across multiple apps, so this will be time consuming to implement; and it's also non-intuitive and easily missed by developers.
  3. Duplicate all the inputs that take arrays to add a variation that only takes the singleton version. The downside here is this will greatly increase the number of inputs, and can cause a lot of confusion, and the tech-debt to implement this on existing queries is near insurmountable. This could only be done for brand new queries, but it feels like an ugly hack.

 

 

In all these work-around options, there is a major downside that it is very easy for developers to miss this edge case, even if we make it a best practice to always use the work-around. Because it's a rare edge case and seldom causes errors to be thrown, defects can easily go undetected for a very long time, and become challenging to trouble-shoot. The only way to detect that these work-arounds weren't applied is with a manual code review.

 

I was wondering if others have encountered these use cases and implemented any design principles or best practices to avoid this pitfall. Even if we have to use some of the above work-arounds on existing tech-debt, we'd like to set a new best practice for designs going forward, especially for query rule development.

 

Thanks!

  Discussion posts and replies are publicly visible

Parents
  • 0
    Certified Lead Developer

    My universalized, reusable Query Entity expression rules almost always use the "applyWhen: a!isNotNullOrEmpty(ri!filterItem)"; i never rely on "ignore filters with empty values" and for the most part pretend that option doesn't exist, as it only begets trouble in my experience.  One of my most-used QE rules accepts a single or array as its primary key identifier for querying and i have basically no issues using it anywhere using just these standard techniques - the only thing I need to watch out for is, any time using it in the wild where the passed value could ever be blank, i null-check it there and pass in "-1" to make sure my query results in no results (instead of all results, which is... problematic).

Reply
  • 0
    Certified Lead Developer

    My universalized, reusable Query Entity expression rules almost always use the "applyWhen: a!isNotNullOrEmpty(ri!filterItem)"; i never rely on "ignore filters with empty values" and for the most part pretend that option doesn't exist, as it only begets trouble in my experience.  One of my most-used QE rules accepts a single or array as its primary key identifier for querying and i have basically no issues using it anywhere using just these standard techniques - the only thing I need to watch out for is, any time using it in the wild where the passed value could ever be blank, i null-check it there and pass in "-1" to make sure my query results in no results (instead of all results, which is... problematic).

Children
  • 0
    Certified Senior Developer
    in reply to Mike Schmitt

    Thanks. We actually do something similar to ensure that if all filters are null, we return no results instead of all results. We use the following pattern for this inside our query entity rules, to ensure they will always have this failsafe in place:

    if(
      or(
        ri!selectAllWhenFiltersNull,
          not(all(
          rule!UTIL_isNullBlankEmpty,
          {your_filters}
        ))
      ),
      your_query_entity,
      todatasubset({})
    )

    (Before we implemented this we'd run into scenarios where unexpected missing values would be treated as null and therefore ignore filters and return ALL data rather than none. As you can imagine, this can cause huge security or UI problems.)

    Part of the problem we've found is that a!isNotNullOrEmpty is not sufficient for determining an empty filter, because if you pass a singleton value into an array input, it will become "{null} ", which is then run as a filter rather than ignored. One work-around is to always put our utility rule that checks that additional case in the applyWhen.