The same query works on the terminal. But for some reason I’m getting an error:
xoperator does not exist: integer[] && text[]
pg.query
is unable to process the expression path && Array[$1]
using this: https://www.postgresql.org/docs/9.2/functions-array.html
exports.getReplies = async function (commentID) {
commentID = parseInt(commentID);
console.log(commentID);
return await db.query(
`
WITH RECURSIVE cte (comment_id, body, user_id, path, reply_to, depth) AS (
SELECT comment_id,
body,
user_id,
array[comment_id] AS path,
reply_to,
1 AS depth
FROM comments
WHERE reply_to IS NULL
UNION ALL
SELECT comments.comment_id,
comments.body,
comments.user_id,
cte.path || comments.comment_id,
comments.reply_to,
cte.depth + 1 AS depth
FROM comments
JOIN cte ON comments.reply_to = cte.comment_id
)
SELECT comment_id, body, user_id, path, depth FROM cte
WHERE path && Array[$1] AND comment_id != $2
ORDER BY path;
`,
[commentID, commentID]
);
};
Advertisement
Answer
This should work:
exports.getReplies = async function (commentID) {
commentID = parseInt(commentID);
console.log(commentID);
return await db.query(
`
WITH RECURSIVE thread AS (
SELECT comment_id, body, user_id
, ARRAY[comment_id] AS path
, reply_to
, 1 AS depth
FROM comments
WHERE reply_to IS NULL
UNION ALL
SELECT c.comment_id, c.body, c.user_id
, t.path || c.comment_id
, c.reply_to
, t.depth + 1 -- AS depth
FROM thread t
JOIN comments c ON c.reply_to = t.comment_id
)
SELECT comment_id, body, user_id, path, depth
FROM thread
WHERE $1::int = ANY(path) -- !!!
AND comment_id <> $2::int
ORDER BY path;
`,
[commentID, commentID]
);
Why?
Short answer: type resolution.
Long answer:
Concluding from the error message that comments.comment_id
is of type integer
. Forming an array from it with ARRAY[comment_id]
produces an integer array: integer[]
, or int[]
for short.
You want int[]
or compatible on the other side, not text[]
. Untyped string literals are passed (I assume, but the solution also works for parameters typed text
or varchar
). When fed to the ARRAY constructor in ARRAY[$1]
, type resolution is forced before a matching type can be deferred from the assignment in the expression. The default type is text
, so you get text[]
. Hence the error message:
error: operator does not exist: integer[] && text[]
Explanation by example:
db<>fiddle here
You can fix this with an explicit cast, either before or after the ARRAY constructor is applied. But it’s slightly cheaper to do it first.
WHERE path && ARRAY[$1::int]
Then again, my alternative, equivalent syntax is cheaper, yet:
WHERE $1::int = ANY(path)
Also, if comments.comment_id
can be NULL (which you also did not specify) or the recursive CTE adds a NULL to the array (the one at hand does not), your original expression with the ARRAY “overlap” operator &&
would fail as it cannot cope with NULL elements. So twice better.
The array operator would make sense to employ a GIN index on an array column. But a derived array column is never indexed, so there is no point in using the more expensive operator. See:
I added explicit casts to integer
for both parameters. Does not hurt, but it’s not necessary in my version of the query – if parameters are passed as untyped string literals (which I assume).