`pg_typeof()` fails for object column expressions (plus some other potential type issue)
See original GitHub issueI 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.
- Just there to fix the ordering as
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
).
- This is the value that CrateDB reports on the console when evaluating the expression in isolation (e.g., with a bare
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.
- This column reports the return value of
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 returnundefined
for null values. -
However, it appears that if you mutate a
NULL
with an identity function, CrateDB will display aNULL
result, butpg_typeof()
seems to think that thisNULL
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 toTRUE,
but if you test the truth value, it behaves likeFALSE
. -
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, likeTIMESTAMPTZ
. However, interestingly, mutating an object column reference does not cause the value to lose its type information like it does withTIMESTAMPTZ
.
Issue Analytics
- State:
- Created 2 years ago
- Comments:8 (8 by maintainers)
Top GitHub Comments
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))
@mfussenegger thanks. This will be helpful when I finish up my rewrite of this section.