question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

`pg_typeof()` fails for object column expressions (plus some other potential type issue)

See original GitHub issue

I am currently updating the documentation for CrateDB data types. I am attempting to add example queries that demonstrate the practical use of each data type (e.g., table creation, inserts, selects, and so on).

I am currently stuck on the section that documents OBJECT(IGNORED).

DYNAMIC objects provide some schema flexibility, but the column schema is fixed once set by an insert. On the other hand, objects with an IGNORED policy allow you to ingest arbitrary documents (i.e., with no set column schema).

One of the distinct advantages of a non-relational database is the ability to insert arbitrary documents and then worry about normalization after the fact. In this sense, my understanding of the IGNORED column policy is that it provides one of the core “NoSQL” features that CrateDB advertises in addition to the benefits of SQL.

Because of this, I want to demonstrate how you might insert objects with different structures and then normalize with a simple SELECT query using conditional logic.

Side-note: If you’re doing normalization in your application code, you can technically wedge “schemaless documents” into any datastore using object serialization or whatever (e.g., Python’s pickle module or PHP’s serialize()). That is why I want to demonstrate normalization using only SQL.

My example use-case

Here’s the example I came up with:

cr> CREATE TABLE my_table (
        title TEXT,
        protagonist OBJECT(IGNORED) AS (
            name TEXT,
            chapter SMALLINT
        )
    );
CREATE OK, 1 row affected  (2.118 sec)
cr> INSERT INTO my_table (
        title,
        protagonist
    ) VALUES (
        'Alice in Wonderland',
        {
            "name" = 'Alice',
            "chapter" = 1,
            "size" = {
                "value" = 10,
                "units" = 'inches'
            }
        }
    );
INSERT OK, 1 row affected  (0.209 sec)
cr> INSERT INTO my_table (
        title,
        protagonist
    ) VALUES (
        'Alice in Wonderland',
        {
            "name" = 'Alice',
            "chapter" = 2,
            "size" = 'As big as a room'
        }
    );
INSERT OK, 1 row affected  (0.069 sec)

Here was my first attempt to normalize the data:

cr> SELECT
        protagonist['name'] AS name,
        protagonist['chapter'] AS chapter,
        IF(pg_typeof(protagonist['size']) = 'text',
            protagonist['size'],
        IF(pg_typeof(protagonist['size']) = 'object',
            format('%s %s',
                protagonist['size']['value'],
                protagonist['size']['units'])
        )) AS size
    FROM my_table
    ORDER BY protagonist['chapter'] ASC;
+-------+---------+------+
| name  | chapter | size |
+-------+---------+------+
| Alice |       1 | NULL |
| Alice |       2 | NULL |
+-------+---------+------+
SELECT 2 rows in set (0.116 sec)

That NULL surprised me. I investigated:

cr> SELECT
        protagonist['name'] AS name,
        protagonist['chapter'] AS chapter,
        pg_typeof(protagonist['size']) AS typeof_size
    FROM my_table
    ORDER BY protagonist['chapter'] ASC;
+-------+---------+-------------+
| name  | chapter | typeof_size |
+-------+---------+-------------+
| Alice |       1 | undefined   |
| Alice |       2 | undefined   |
+-------+---------+-------------+
SELECT 2 rows in set (0.006 sec)

I did not expect pg_typeof() to return undefined. I tested pg_typeof() extensively to figure out what was going on. I have tabulated the results of my experimentation in the next section.

Getting back to my example query, I tried TRY_CAST next:

cr> SELECT
        protagonist['name'] as name,
        protagonist['chapter'] as chapter,
        IF(TRY_CAST(protagonist['size'] AS TEXT) IS NOT NULL,
            protagonist['size'],
        IF(TRY_CAST(protagonist['size'] AS OBJECT) IS NOT NULL,
            format('%s %s',
                protagonist['size']['value'],
                protagonist['size']['units'])
        )) AS size
    FROM my_table
    ORDER BY protagonist['chapter'] ASC;
+-------+---------+-------------------------------+
| name  | chapter | size                          |
+-------+---------+-------------------------------+
| Alice |       1 | {"units":"inches","value":10} |
| Alice |       2 | As big as a room              |
+-------+---------+-------------------------------+
SELECT 2 rows in set (0.008 sec)

That’s not what I expected. The fact that objects can be cast to strings prevents this method from working.

I tried to switch the order of the tests.

Side-note: Ideally, in my opinion, you could do type introspection tests in any order. However, I have enough experience with PHP and JavaScript to be familiar with the trade-offs you have to make for soft typing, haha.

In any case, I ran into a new error:

cr> SELECT
        protagonist['name'] as name,
        protagonist['chapter'] as chapter,
        IF(TRY_CAST(protagonist['size'] AS OBJECT) IS NOT NULL,
            format('%s %s',
                protagonist['size']['value'],
                protagonist['size']['units']),
        IF(TRY_CAST(protagonist['size'] AS TEXT) IS NOT NULL,
            protagonist['size']
        )) AS size
    FROM my_table
    ORDER BY protagonist['chapter'] ASC;
+-------+---------+-------------------------------+
RuntimeException[com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'As': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
 at [Source: (StringReader); line: 1, column: 3]]

The source of the error:

cr> SELECT TRY_CAST('foo' AS OBJECT);
RuntimeException[com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'foo': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
 at [Source: (StringReader); line: 1, column: 4]]

If you try to cast a string to an object, CrateDB attempts to parse the string as a JSON blob.

Given that pg_typeof() seems to fail for object column values and TRY_CAST isn’t useful for the type of introspection I need to normalize this data, I have run out of (sensible) ideas for a solution.

This (awful) hack “works”:

cr> SELECT
        protagonist['name'] as name,
        protagonist['chapter'] as chapter,
        IF(
            length(trim(BOTH '{}' from protagonist['size']::TEXT)) <
            length(protagonist['size']::TEXT),
                format('%s %s',
                    protagonist['size']['value'],
                    protagonist['size']['units']),
                protagonist['size']) AS size
    FROM my_table
    ORDER BY protagonist['chapter'] ASC;
+-------+---------+------------------+
| name  | chapter | size             |
+-------+---------+------------------+
| Alice |       1 | 10 inches        |
| Alice |       2 | As big as a room |
+-------+---------+------------------+
SELECT 2 rows in set (0.010 sec)

But I’m not putting that in the docs… 😉

My pg_typeof() experiments

As for pg_typeof(), my working theory is that there is something specific about values produced by expressions that reference object columns that cause the function to return undefined.

To be specific, this works:

pg_typeof(<any expression that doesn't reference an object column>)

But this doesn’t work:

pg_typeof(<any expression references an object column>)

To test my theory that it’s something about the expression referencing an object column, I tried mutating the value with an identity function and then passing that to pg_typeof().

So, for example, if you want to test my_object['column'] with pg_typeof() (which will fail) and the value of my_object['column'] is 1, mutate the value by performing any identity function and then test that. In this case, you could do my_object['column'] * 1 (any number multiplied by one is itself).

If you mutate the value with an identity function, the (as far as I can tell, completely hidden) information about the value coming from an object column is lost (i.e., it appears to “wash” the value somehow) and pg_typeof() will return the correct result.

I have prepared some queries that demonstrate this. There is one query per basic category of primitive type.

Side note: I experimented a little with testing container types and geospatial types, but I abandoned that on the premise that my experiments with the primitive types will be enough to diagnose the problem.

However, my tests revealed some unrelated peculiarities related to identity functions (details below). There may be additional peculiarities with identity functions for container types and geospatial types. This issue is big enough, so I will leave that up to someone else to investigate if it is of interest.

The tables columns are as follows:

  • test #
    • Just there to fix the ordering as UNION ALL does not preserve statement ordering.
  • expression
    • The expression I am testing, formatted by hand. (I don’t think there is any way to serialize expressions for printing.)
  • test value
    • This is the value that CrateDB reports on the console when evaluating the expression in isolation (e.g., with a bare SELECT).
  • test result
    • The value that CrateDB reports on the console does not always test positive if you ask CrateDB to confirm the value (very confusing!). This column indicates a pass or fail for this simple test.
  • mutated
    • The expressions in each table come in pairs: the basic expression and the basic expression mutated with an identity function. This column is just for convenience and indicates whether the value being tested has been mutated or not.
  • pg_typeof()
    • This column reports the return value of pg_typeof() for the test expression.

Nulls

cr> SELECT
        test_num    AS "test #",
        expr        AS "expression",
        test_val    AS "test value",
        test_result AS "test result",
        mutated     AS "mutated",
        typeof      AS "pg_typeof()"
    FROM (
        SELECT
            1                                                AS test_num,
            'NULL'                                           AS expr,
            NULL                                             AS test_val,
            IF(NULL IS NULL, '✔︎', '✘')                       AS test_result,
            '✘'                                              AS mutated,
            pg_typeof(NULL)                                  AS typeof
        UNION ALL SELECT
            2                                                AS test_num,
            'NULL + NULL'                                    AS expr,
            NULL                                             AS test_val,
            IF((NULL + NULL) IS NULL, '✔︎', '✘')              AS test_result,
            '✔︎'                                              AS mutated,
            pg_typeof(NULL + NULL)                           AS typeof
        UNION ALL SELECT
            3                                                AS test_num,
            '[NULL][1]'                                      AS expr,
            NULL                                             AS test_val,
            IF([NULL][1] IS NULL, '✔︎', '✘')                  AS test_result,
            '✘'                                              AS mutated,
            pg_typeof([NULL][1])                             AS typeof
        UNION ALL SELECT
            4                                                AS test_num,
            '[NULL][1] + NULL'                               AS expr,
            NULL                                             AS test_val,
            IF(([NULL][1] + NULL) IS NULL, '✔︎', '✘')         AS test_result,
            '✔︎'                                              AS mutated,
            pg_typeof([NULL][1] + NULL)                      AS typeof
        UNION ALL SELECT
            5                                                AS test_num,
            '{ a = NULL }[''a'']'                            AS expr,
            NULL                                             AS test_val,
            IF({ a = NULL }['a'] IS NULL, '✔︎', '✘')          AS test_result,
            '✘'                                              AS mutated,
            pg_typeof({ a = NULL }['a'])                     AS typeof
        UNION ALL SELECT
            6                                                AS test_num,
            '{ a = NULL }[''a''] + NULL'                     AS expr,
            NULL                                             AS test_val,
            IF(({ a = NULL }['a'] + NULL) IS NULL, '✔︎', '✘') AS test_result,
            '✔︎'                                              AS mutated,
            pg_typeof({ a = NULL }['a'] + NULL)              AS typeof
    ) AS t
    ORDER BY test_num ASC;
+--------+--------------------------+------------+-------------+---------+-------------+
| test # | expression               | test value | test result | mutated | pg_typeof() |
+--------+--------------------------+------------+-------------+---------+-------------+
|      1 | NULL                     |       NULL | ✔︎           | ✘       | undefined   |
|      2 | NULL + NULL              |       NULL | ✔︎           | ✔︎       | integer     |
|      3 | [NULL][1]                |       NULL | ✔︎           | ✘       | undefined   |
|      4 | [NULL][1] + NULL         |       NULL | ✔︎           | ✔︎       | integer     |
|      5 | { a = NULL }['a']        |       NULL | ✔︎           | ✘       | undefined   |
|      6 | { a = NULL }['a'] + NULL |       NULL | ✔︎           | ✔︎       | integer     |
+--------+--------------------------+------------+-------------+---------+-------------+
SELECT 6 rows in set (0.006 sec)

Observations:

  • In this instance, it is correct for pg_typeof() to return undefined for null values.

  • However, it appears that if you mutate a NULL with an identity function, CrateDB will display a NULL result, but pg_typeof() seems to think that this NULL is an integer. I presume there is some hidden information that causes this.

Booleans

cr> SELECT
        test_num    AS "test #",
        expr        AS "expression",
        test_val    AS "test value",
        test_result AS "result",
        mutated     AS "mutated",
        typeof      AS "pg_typeof()"
    FROM (
        SELECT
            1                                         AS test_num,
            'TRUE'                                    AS expr,
            TRUE                                      AS test_val,
            IF(TRUE, '✔︎', '✘')                        AS test_result,
            '✘'                                       AS mutated,
            pg_typeof(TRUE)                           AS typeof
        UNION ALL SELECT
            2                                         AS test_num,
            'TRUE AND TRUE'                           AS expr,
            TRUE                                      AS test_val,
            IF(TRUE AND TRUE, '✔︎', '✘')               AS test_result,
            '✔'                                       AS mutated,
            pg_typeof(TRUE AND TRUE)                  AS typeof
        UNION ALL SELECT
            3                                         AS test_num,
            '[TRUE][1]'                               AS expr,
            TRUE                                      AS test_val,
            IF([TRUE][1], '✔︎', '✘')                   AS test_result,
            '✘'                                       AS mutated,
            pg_typeof([TRUE][1])                      AS typeof
        UNION ALL SELECT
            4                                         AS test_num,
            '[TRUE][1] AND TRUE'                      AS expr,
            TRUE                                      AS test_val,
            IF([TRUE][1] AND TRUE, '✔︎', '✘')          AS test_result,
            '✔'                                       AS mutated,
            pg_typeof([TRUE][1] AND TRUE)             AS typeof
        UNION ALL SELECT
            5                                         AS test_num,
            '{ a = TRUE }[''a'']'                     AS expr,
            TRUE                                      AS test_val,
            IF({ a = NULL }['a'], '✔︎', '✘')           AS test_result,
            '✘'                                       AS mutated,
            pg_typeof({ a = TRUE }['a'])              AS typeof
        UNION ALL SELECT
            6                                         AS test_num,
            '{ a = TRUE }[''a''] AND TRUE'            AS expr,
            TRUE                                      AS test_val,
            IF({ a = TRUE }['a'] AND TRUE, '✔︎', '✘')  AS test_result,
            '✔'                                       AS mutated,
            pg_typeof({ a = TRUE }['a'] AND TRUE)     AS typeof
    ) AS t
    ORDER BY test_num ASC;
+--------+----------------------------+------------+--------+---------+-------------+
| test # | expression                 | test value | result | mutated | pg_typeof() |
+--------+----------------------------+------------+--------+---------+-------------+
|      1 | TRUE                       | TRUE       | ✔︎      | ✘       | boolean     |
|      2 | TRUE AND TRUE              | TRUE       | ✔︎      | ✔       | boolean     |
|      3 | [TRUE][1]                  | TRUE       | ✔︎      | ✘       | boolean     |
|      4 | [TRUE][1] AND TRUE         | TRUE       | ✔︎      | ✔       | boolean     |
|      5 | { a = TRUE }['a']          | TRUE       | ✘      | ✘       | undefined   |
|      6 | { a = TRUE }['a'] AND TRUE | TRUE       | ✔︎      | ✔       | boolean     |
+--------+----------------------------+------------+--------+---------+-------------+
SELECT 6 rows in set (0.010 sec)

Observations:

  • CrateDB will tell you that { a = TRUE }['a'] evaluates to TRUE, but if you test the truth value, it behaves like FALSE.

  • pg_typeof() fails for the object column expressions but does not fail for mutated object column expressions.

Strings

cr> SELECT
        test_num    AS "test #",
        expr        AS "expression",
        test_val    AS "test value",
        test_result AS "result",
        mutated     AS "mutated",
        typeof      AS "pg_typeof()"
    FROM (
        SELECT
            1                                                          AS test_num,
            '''foo''::TEXT'                                            AS expr,
            '''foo'''                                                  AS test_val,
            IF('foo'::TEXT = 'foo', '✔︎', '✘')                          AS test_result,
            '✘'                                                        AS mutated,
            pg_typeof('foo'::TEXT)                                     AS typeof
        UNION ALL SELECT
            2                                                          AS test_num,
            'concat('''', ''foo''::TEXT)'                              AS expr,
            '''foo'''                                                  AS test_val,
            IF(concat('', 'foo'::TEXT) = 'foo', '✔︎', '✘')              AS test_result,
            '✔'                                                        AS mutated,
            pg_typeof(concat('', 'foo'::TEXT))                         AS typeof
        UNION ALL SELECT
            3                                                          AS test_num,
            '[''foo''::TEXT][1]'                                       AS expr,
            '''foo'''                                                  AS test_val,
            IF(['foo'::TEXT][1] = 'foo', '✔︎', '✘')                     AS test_result,
            '✘'                                                        AS mutated,
            pg_typeof(['foo'::TEXT][1])                                AS typeof
        UNION ALL SELECT
            4                                                          AS test_num,
            'concat('''', [''foo''::TEXT][1])'                         AS expr,
            '''foo'''                                                  AS test_val,
            IF(concat('', ['foo'::TEXT][1]) = 'foo', '✔︎', '✘')         AS test_result,
            '✔'                                                        AS mutated,
            pg_typeof(concat('', ['foo'::TEXT][1]))                    AS typeof
        UNION ALL SELECT
            5                                                          AS test_num,
            '{ a = ''foo''::TEXT }[''a'']'                             AS expr,
            '''foo'''                                                  AS test_val,
            IF({ a = 'foo'::TEXT }['a'] = 'foo', '✔︎', '✘')             AS test_result,
            '✘'                                                        AS mutated,
            pg_typeof({ a = 'foo'::TEXT }['a'])                        AS typeof
        UNION ALL SELECT
            6                                                          AS test_num,
            'concat('''', { a = ''foo''::TEXT }[''a''])'               AS expr,
            '''foo'''                                                  AS test_val,
            IF(concat('', { a = 'foo'::TEXT }['a']) = 'foo', '✔︎', '✘') AS test_result,
            '✔'                                                        AS mutated,
            pg_typeof(concat('', { a = 'foo'::TEXT }['a']))            AS typeof
    ) AS t
    ORDER BY test_num;
+--------+--------------------------------------+------------+--------+---------+-------------+
| test # | expression                           | test value | result | mutated | pg_typeof() |
+--------+--------------------------------------+------------+--------+---------+-------------+
|      1 | 'foo'::TEXT                          | 'foo'      | ✔︎      | ✘       | text        |
|      2 | concat('', 'foo'::TEXT)              | 'foo'      | ✔︎      | ✔       | text        |
|      3 | ['foo'::TEXT][1]                     | 'foo'      | ✔︎      | ✘       | text        |
|      4 | concat('', ['foo'::TEXT][1])         | 'foo'      | ✔︎      | ✔       | text        |
|      5 | { a = 'foo'::TEXT }['a']             | 'foo'      | ✔︎      | ✘       | undefined   |
|      6 | concat('', { a = 'foo'::TEXT }['a']) | 'foo'      | ✔︎      | ✔       | text        |
+--------+--------------------------------------+------------+--------+---------+-------------+
SELECT 6 rows in set (0.007 sec)

Observations:

  • pg_typeof() fails for the object column expressions but does not fail for mutated object column expressions.

Numbers

cr> SELECT
        test_num    AS "test #",
        expr        AS "expression",
        test_val    AS "test value",
        test_result AS "result",
        mutated     AS "mutated",
        typeof      AS "pg_typeof()"
    FROM (
        SELECT
            1                                             AS test_num,
            '1::INTEGER'                                  AS expr,
            '1'                                           AS test_val,
            IF(1::INTEGER = 1, '✔︎', '✘')                  AS test_result,
            '✘'                                           AS mutated,
            pg_typeof(1::INTEGER)                         AS typeof
        UNION ALL SELECT
            2                                             AS test_num,
            '1::INTEGER * 1'                              AS expr,
            '1'                                           AS test_val,
            IF(1::INTEGER * 1 = 1, '✔︎', '✘')              AS test_result,
            '✔'                                           AS mutated,
            pg_typeof(1::INTEGER * 1)                     AS typeof
        UNION ALL SELECT
            3                                             AS test_num,
            '[1::INTEGER][1]'                             AS expr,
            '1'                                           AS test_val,
            IF([1::INTEGER][1] = 1, '✔︎', '✘')             AS test_result,
            '✘'                                           AS mutated,
            pg_typeof([1::INTEGER][1])                    AS typeof
        UNION ALL SELECT
            4                                             AS test_num,
            '[1::INTEGER][1] * 1'                         AS expr,
            '1'                                           AS test_val,
            IF([1::INTEGER][1] * 1 = 1, '✔︎', '✘')         AS test_result,
            '✔'                                           AS mutated,
            pg_typeof([1::INTEGER][1] * 1)                AS typeof
        UNION ALL SELECT
            5                                             AS test_num,
            '{ a = 1::INTEGER }[''a'']'                   AS expr,
            '1'                                           AS test_val,
            IF({ a = 1::INTEGER }['a'] = 1, '✔︎', '✘')     AS test_result,
            '✘'                                           AS mutated,
            pg_typeof({ a = 1::INTEGER }['a'])            AS typeof
        UNION ALL SELECT
            6                                             AS test_num,
            '{ a = 1::INTEGER }[''a''] * 1'               AS expr,
            '1'                                           AS test_val,
            IF({ a = 1::INTEGER }['a'] * 1 = 1, '✔︎', '✘') AS test_result,
            '✔'                                           AS mutated,
            pg_typeof({ a = 1::INTEGER }['a'] * 1)        AS typeof
    ) AS t
    ORDER BY test_num;
+--------+-----------------------------+------------+--------+---------+-------------+
| test # | expression                  | test value | result | mutated | pg_typeof() |
+--------+-----------------------------+------------+--------+---------+-------------+
|      1 | 1::INTEGER                  | 1          | ✔︎      | ✘       | integer     |
|      2 | 1::INTEGER * 1              | 1          | ✔︎      | ✔       | integer     |
|      3 | [1::INTEGER][1]             | 1          | ✔︎      | ✘       | integer     |
|      4 | [1::INTEGER][1] * 1         | 1          | ✔︎      | ✔       | integer     |
|      5 | { a = 1::INTEGER }['a']     | 1          | ✔︎      | ✘       | undefined   |
|      6 | { a = 1::INTEGER }['a'] * 1 | 1          | ✔︎      | ✔       | integer     |
+--------+-----------------------------+------------+--------+---------+-------------+
SELECT 6 rows in set (0.007 sec)

Observations:

  • pg_typeof() fails for the object column expressions but does not fail for mutated object column expressions.

Dates and times

cr> SELECT
        test_num    AS "test #",
        expr        AS "expression",
        test_val    AS "test value",
        test_result AS "result",
        mutated     AS "mutated",
        typeof      AS "pg_typeof()"
    FROM (
        SELECT
            1                                                 AS test_num,
            '1::TIMESTAMPTZ'                                  AS expr,
            '1'                                               AS test_val,
            IF(1::TIMESTAMPTZ = 1, '✔︎', '✘')                  AS test_result,
            '✘'                                               AS mutated,
            pg_typeof(1::TIMESTAMPTZ)                         AS typeof
        UNION ALL SELECT
            2                                                 AS test_num,
            '1::TIMESTAMPTZ + 0'                              AS expr,
            '1'                                               AS test_val,
            IF(1::TIMESTAMPTZ + 0 = 1, '✔︎', '✘')              AS test_result,
            '✔'                                               AS mutated,
            pg_typeof(1::TIMESTAMPTZ * 1)                     AS typeof
        UNION ALL SELECT
            3                                                 AS test_num,
            '[1::TIMESTAMPTZ][1]'                             AS expr,
            '1'                                               AS test_val,
            IF([1::TIMESTAMPTZ][1] = 1, '✔︎', '✘')             AS test_result,
            '✘'                                               AS mutated,
            pg_typeof([1::TIMESTAMPTZ][1])                    AS typeof
        UNION ALL SELECT
            4                                                 AS test_num,
            '[1::TIMESTAMPTZ][1] + 0'                         AS expr,
            '1'                                               AS test_val,
            IF([1::TIMESTAMPTZ][1] + 0 = 1, '✔︎', '✘')         AS test_result,
            '✔'                                               AS mutated,
            pg_typeof([1::TIMESTAMPTZ][1] * 1)                AS typeof
        UNION ALL SELECT
            5                                                 AS test_num,
            '{ a = 1::TIMESTAMPTZ }[''a'']'                   AS expr,
            '1'                                               AS test_val,
            IF({ a = 1::TIMESTAMPTZ }['a'] = 1, '✔︎', '✘')     AS test_result,
            '✘'                                               AS mutated,
            pg_typeof({ a = 1::TIMESTAMPTZ }['a'])            AS typeof
        UNION ALL SELECT
            6                                                 AS test_num,
            '{ a = 1::TIMESTAMPTZ }[''a''] + 0'               AS expr,
            '1'                                               AS test_val,
            IF({ a = 1::TIMESTAMPTZ }['a'] + 0 = 1, '✔︎', '✘') AS test_result,
            '✔'                                               AS mutated,
            pg_typeof({ a = 1::TIMESTAMPTZ }['a'] * 1)        AS typeof
    ) AS t
    ORDER BY test_num;
+--------+---------------------------------+------------+--------+---------+--------------------------+
| test # | expression                      | test value | result | mutated | pg_typeof()              |
+--------+---------------------------------+------------+--------+---------+--------------------------+
|      1 | 1::TIMESTAMPTZ                  | 1          | ✔︎      | ✘       | timestamp with time zone |
|      2 | 1::TIMESTAMPTZ + 0              | 1          | ✔︎      | ✔       | timestamp with time zone |
|      3 | [1::TIMESTAMPTZ][1]             | 1          | ✔︎      | ✘       | timestamp with time zone |
|      4 | [1::TIMESTAMPTZ][1] + 0         | 1          | ✔︎      | ✔       | timestamp with time zone |
|      5 | { a = 1::TIMESTAMPTZ }['a']     | 1          | ✔︎      | ✘       | undefined                |
|      6 | { a = 1::TIMESTAMPTZ }['a'] + 0 | 1          | ✔︎      | ✔       | integer                  |
+--------+---------------------------------+------------+--------+---------+--------------------------+
SELECT 6 rows in set (0.021 sec)

Observations:

  • pg_typeof() fails for the object column expressions but does not fail for mutated object column expressions.

  • If you mutate a TIMESTAMPTZ from an object column, pg_typeof() believes the new value is an integer.

    I know that timestamps are stored internally as BIGINT values, so it appears that the value somehow loses some hidden information about its “true” or “intended” type.

    I am guessing this may affect other date and time types.

IP addresses

cr> SELECT
        test_num    AS "test #",
        expr        AS "expression",
        test_val    AS "test value",
        test_result AS "result",
        mutated     AS "mutated",
        typeof      AS "pg_typeof()"
    FROM (
        SELECT
            1                                                            AS test_num,
            '1::IP'                                                      AS expr,
            '''0.0.0.1'''                                                AS test_val,
            IF(1::IP = '0.0.0.1', '✔︎', '✘')                              AS test_result,
            '✘'                                                          AS mutated,
            pg_typeof(1::IP)                                             AS typeof
        UNION ALL SELECT
            2                                                            AS test_num,
            'concat('''', 1::IP)::IP'                                    AS expr,
            '''0.0.0.1'''                                                AS test_val,
            IF(concat('', 1::IP)::IP = '0.0.0.1', '✔︎', '✘')              AS test_result,
            '✔'                                                          AS mutated,
            pg_typeof(concat('', 1::IP)::IP)                             AS typeof
        UNION ALL SELECT
            3                                                            AS test_num,
            '[1::IP][1]'                                                 AS expr,
            '''0.0.0.1'''                                                AS test_val,
            IF([1::IP][1] = '0.0.0.1', '✔︎', '✘')                         AS test_result,
            '✘'                                                          AS mutated,
            pg_typeof([1::IP][1])                                        AS typeof
        UNION ALL SELECT
            4                                                            AS test_num,
            'concat('''', [1::IP][1])::IP'                               AS expr,
            '''0.0.0.1'''                                                AS test_val,
            IF(concat('', [1::IP][1])::IP = '0.0.0.1', '✔︎', '✘')         AS test_result,
            '✔'                                                          AS mutated,
            pg_typeof(concat('', [1::IP][1])::IP)                        AS typeof
        UNION ALL SELECT
            5                                                            AS test_num,
            '{ a = 1::IP }[''a'']'                                       AS expr,
            '''0.0.0.1'''                                                AS test_val,
            IF({ a = 1::IP }['a'] = '0.0.0.1', '✔︎', '✘')                 AS test_result,
            '✘'                                                          AS mutated,
            pg_typeof({ a = 1::IP }['a'])                                AS typeof
        UNION ALL SELECT
            6                                                            AS test_num,
            'concat('''', { a = 1::IP }[''a''])::IP'                     AS expr,
            '''0.0.0.1'''                                                AS test_val,
            IF(concat('', { a = 1::IP }['a'])::IP = '0.0.0.1', '✔︎', '✘') AS test_result,
            '✔'                                                          AS mutated,
            pg_typeof(concat('', { a = 1::IP }['a'])::IP)                AS typeof
    ) AS t
    ORDER BY test_num;
+--------+------------------------------------+------------+--------+---------+-------------+
| test # | expression                         | test value | result | mutated | pg_typeof() |
+--------+------------------------------------+------------+--------+---------+-------------+
|      1 | 1::IP                              | '0.0.0.1'  | ✔︎      | ✘       | ip          |
|      2 | concat('', 1::IP)::IP              | '0.0.0.1'  | ✔︎      | ✔       | ip          |
|      3 | [1::IP][1]                         | '0.0.0.1'  | ✔︎      | ✘       | ip          |
|      4 | concat('', [1::IP][1])::IP         | '0.0.0.1'  | ✔︎      | ✔       | ip          |
|      5 | { a = 1::IP }['a']                 | '0.0.0.1'  | ✔︎      | ✘       | undefined   |
|      6 | concat('', { a = 1::IP }['a'])::IP | '0.0.0.1'  | ✔︎      | ✔       | ip          |
+--------+------------------------------------+------------+--------+---------+-------------+
SELECT 6 rows in set (0.007 sec)

Observations:

  • pg_typeof() fails for the object column expressions but does not fail for mutated object column expressions.

  • I know that IPs are stored as BIGINT values internally, like TIMESTAMPTZ. However, interestingly, mutating an object column reference does not cause the value to lose its type information like it does with TIMESTAMPTZ.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:8 (8 by maintainers)

github_iconTop GitHub Comments

1reaction
mfusseneggercommented, Jul 12, 2021

Instead of me inventing hypothetical use-cases to explain why I think this would be a useful feature, I can instead say that without this feature, I am not sure I understand the use-case for OBJECT(IGNORED).

  • Avoiding indexing overhead if you never filter on the columns and don’t use them in aggregations.
  • Just retrieving the data and then process them in an application.

You’re right - it is very similar to storing a JSON blob as text.

(To be clear, I’m not saying that the sensor use-case you mentioned isn’t legitimate, it’s just that doing what you’re suggesting comes with some trade-offs and whether they’re acceptable or not depends on the requirements of the complete use case. How will people use the data, why are they storing it in the first place and so on. Some people like to skip thinking about it and just store data, but storing data without eventually making sense of it is pretty useless))

0reactions
nomirosecommented, Jul 13, 2021

@mfussenegger thanks. This will be helpful when I finish up my rewrite of this section.

Read more comments on GitHub >

github_iconTop Results From Across the Web

postgresql - Does pg_typeof ever return alternative type names?
I have a sql function that takes an array of strings. Those strings are sql expressions that are stored in a table and...
Read more >
How pg_typeof() Works in PostgreSQL - Database.Guide
It returns a regtype , which is an OID alias type. Therefore it's the same as an OID for comparison purposes but displays...
Read more >
Documentation: 9.3: System Information Functions - PostgreSQL
If the argument is not of a collatable data type, then an error is raised. The functions shown in Table 9-56 extract comments...
Read more >
PostgreSQL - Data Type - Tutorialspoint
In this chapter, we will discuss about the data types used in PostgreSQL. While creating table, for each column, you specify a data...
Read more >
Postgres Blog - Bruce Momjian
There are some tasks that are common to all database: sql, backups, fail over, performance monitoring, but it would be nice if there...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found