Dynamically deciding which operator to use based on the type of the value at hand

All we need is an easy explanation of the problem, so here it is.

I’m currently implementing a translation between a proprietary expression language and postgres queries at work. So far a lot of stuff fell in place quite nicely, but now I have trouble with one function.
The source language contains a function (contains) that checks whether the first argument contains the second. That means for

  • strings: It checks whether the second arg is a substring of the first arg.
  • arrays: The second arg is an element of the array (first arg)
  • objects/maps/dicts: The second argument is a key of the object (first arg)

Now comes the issue: The translation doesn’t know anything about the DB schema, so the queries need to be generic enough to work on different kinds of schemas. So I don’t know whether a given field is of a specific type. It is okay to fail in some cases, but I would at least like the code to work for the types text/varchar , text[] and jsonb. But I’m struggling to get even the first two to work.
Here is the (incomplete) WHERE clause of a query that is generated by the translator:

    WHEN pg_typeof(entity.field1)::text = ANY('{text,character varying}') THEN ( entity.field1 LIKE '%' || entity.field2|| '%' )
    WHEN pg_typeof(entity.field1)::text = 'text[]' THEN ( entity.field2=ANY(entity.field1))
    ELSE false

Let’s assume (for now) that entity.field2 is a text column. entity.field1 is

  • a) a text column
  • b) a text[] column

The query works fine for a), as expected. But it fails for b) with:

operator does not exist: text[] ~~ text

So I broke the query down to:

    WHEN false THEN ( entity.field1 LIKE '%' || entity.field2 || '%' )
    ELSE false

For a) false is returned, as expected. For b) the error is still thrown. So it seems like the operator type check is run, even if there is no way to reach the code in the THEN clause.

I tried to find the reason for that in the docs but I didn’t. This paragraph contains some info on early evaluation, but not related to operator type checks.

So, is there a way to get this to work by catching errors, casting (have tried naively casting to text, that works for text[] but not for jsonb), or anything else?

Thanks in advance for your help, it is greatly appreciated.

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

So after some helpful comments here and from my colleagues at work I ended up creating functions (with overloading) to solve the issue:

CREATE OR REPLACE FUNCTION contains(haystack jsonb, needle text) RETURNS boolean AS $$
      result boolean;
      CASE jsonb_typeof(haystack)
        WHEN 'array', 'object' THEN
          result := haystack ? needle;
          result := haystack LIKE '%' || needle || '%';
      END CASE;
      RETURN result;
  $$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION contains(haystack text, needle text) RETURNS boolean AS $$
    RETURN haystack LIKE '%' || needle || '%';
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION contains(haystack text[], needle text) RETURNS boolean AS $$
    RETURN needle = ANY(haystack);
$$ LANGUAGE plpgsql;

Then I can just include contains(entity.field1, entity.field2) in my query and it works for all three types I need right now. I can also easily extend it to work for more types.

Thanks for all your help

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply