Open some1chan opened 1 year ago
Just ran into this today with a query that looks something like this:
select
sum(price * amount)
from
products
inner join
unnest(array[
('473907d1-0bbc-4cac-b40d-197cc467d06d', 2),
('ca0ad1f8-a038-448f-a322-d6985192a7ec', 1)
]::id_and_amount[]) selected
on products.id = selected.id;
It's really quite inconvenient that we have to do this. Hope this could be more prioritized :+1:
EDIT: Turns out I can pass the array as a JSON array in my case and convert it back into an array of records in the query. That makes things nicer but still definitely not ideal.
Ran into this issue myself today.
To save time for anyone else, here's a reusable function for handling this. Extend/modify as needed!
Note that I'm casting with ::text[]
here. You might need to cast to something else.
import { SQL, sql } from "drizzle-orm";
/**
* Creates a raw SQL array.
* Prevents Drizzle's default array behavior.
* Input is still sanitized.
*
* Source: https://www.answeroverflow.com/m/1155016104721272925
*/
export function createRawSqlArray(itemList: string[]) {
const sanitizedItems: SQL<unknown>[] = [sql`'{`];
const rawItems: SQL<unknown>[] = [];
for (const item of itemList) {
rawItems.push(sql.raw(sql`${item}`.queryChunks[1]!.toString()));
}
sanitizedItems.push(sql.join(rawItems, sql`, `));
sanitizedItems.push(sql`}'::text[]`);
return sql.join(sanitizedItems);
}
Ran into this issue myself today.
To save time for anyone else, here's a reusable function for handling this. Extend/modify as needed!
Note that I'm casting with
::text[]
here. You might need to cast to something else.import { SQL, sql } from "drizzle-orm"; /** * Creates a raw SQL array. * Prevents Drizzle's default array behavior. * Input is still sanitized. * * Source: https://www.answeroverflow.com/m/1155016104721272925 */ export function createRawSqlArray(itemList: string[]) { const sanitizedItems: SQL<unknown>[] = [sql`'{`]; const rawItems: SQL<unknown>[] = []; for (const item of itemList) { rawItems.push(sql.raw(sql`${item}`.queryChunks[1]!.toString())); } sanitizedItems.push(sql.join(rawItems, sql`, `)); sanitizedItems.push(sql`}'::text[]`); return sql.join(sanitizedItems); }
Works like a charm 💯
For me this function isn't ideal,
I needed to be able to add different casts.
In addition, the function did not work with all values.
For example postgresql didnt liked me when I wanted to use this array: ["{foo}bar", "foo'bar", "foo\"bar"]
After checking, it turned out that the query look like this:
const qs = () =>
db.execute(
sql`SELECT unnest(${createRawSqlArray(["{foo}bar", "foo'bar", 'foo"bar'])}) as name`
);
const q = qs().getQuery();
console.log(q.sql); // SELECT unnest('{{foo}bar, foo'bar, foo"bar}'::text[]) as name
There is not quotes!
After my modification query look like this:
SELECT unnest('{"{foo}bar", "foo''bar", "foo\"bar"}'::text[]) as name
My modified function:
import { SQL, sql } from "drizzle-orm";
export function createRawSqlArray(
itemList: string[],
castTo: "text" | "integer" | "timestamp"
) {
const sanitizedItems: SQL<unknown>[] = [sql`'{`];
const rawItems: SQL<unknown>[] = [];
for (const item of itemList) {
// This weird code is to sanitize the input
const v = item
.replaceAll(/'/g, "''")
.replaceAll(/\\/g, "\\\\")
.replaceAll(/"/g, '\\"');
const withQuotes= `"${v}"`;
rawItems.push(sql.raw(sql`${withQuotes}`.queryChunks[1]!.toString()));
}
sanitizedItems.push(sql.join(rawItems, sql`, `));
sanitizedItems.push(sql`}'`);
if (castTo === "integer") sanitizedItems.push(sql`::integer[]`);
else if (castTo === "text") sanitizedItems.push(sql`::text[]`);
else if (castTo === "timestamp") sanitizedItems.push(sql`::timestamp[]`);
else throw new Error("Invalid castTo");
return sql.join(sanitizedItems);
}
I am not sure if the sanitisation fully works. I would be grateful for feedback.
Works like a charm 💯
Really? With this approach we get harcoded array items which ruins prepared statements:
-- getting
AND skill = ANY('{PHP, HTML}'::text[]))
-- should be
AND skill = ANY($1))
Describe what you want
Currently, the default behavior of Drizzle will turn arrays into either its singular element, or a record. This behavior is unintuitive, when you're trying to pass in an array in a PostgreSQL function.
For example, let's say you create a Postgres function called
insert_vote
......and you wish to interact with it with Drizzle.
What you'll see in your logs is this:
For some reason, our array of our one item becomes our one item. We'll run into the
record
type if our array has more than one element. You can get the weirdrecord
behavior by just adding anotheruuid
into the array.This isn't very useful for us in our use case here.
Since Drizzle doesn't support the syntax around arrays passed to functions, we'll need to do this workaround:
This will finally output the right query:
Obviously, this isn't ideal. I'd personally love to see support for passing arrays into functions in Drizzle, as this will streamline the above code to simply be our original example.