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...
CREATE OR REPLACE FUNCTION public.insert_vote(
IN poll_id uuid,
IN voting_user_id uuid,
IN choice_ids uuid[]
) RETURNS TABLE (
status text,
existing_choices uuid[],
votes_created uuid[],
votes_deleted uuid[],
max_votes smallint
) AS $BODY$
BEGIN
-- Left empty
END;
$BODY$ LANGUAGE plpgsql;
...and you wish to interact with it with Drizzle.
import "dotenv/config";
import postgres from 'postgres';
import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';
const client = postgres({
hostname: process.env.POSTGRES_HOST,
database: process.env.POSTGRES_DB,
username: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
port: process.env.POSTGRES_PORT
? Number(process.env.POSTGRES_PORT)
: 5432,
});
const db = drizzle(client, { schema, logger: new DefaultLogger() });
// IDs are the same, but this won't matter for our example. These are also ULIDs, and not UUIDs,
// but this also won't matter as Postgres doesn't do any validation on these.
const pollHexId = "018abb841f4ae4257adbba232b34f848";
const userId = "018abb841f4ae4257adbba232b34f848";
const choiceIds = ["018abb841f4ae4257adbba232b34f848"];
const sqlQuery = sql`SELECT public.insert_vote(${pollHexId}::uuid, ${userId}::uuid, ${choiceIds}::uuid[]);`;
console.log((await import('util')).inspect(sqlQuery, undefined, 5));
const insertVote = await db.execute(sqlQuery);
console.log("Finished!");
What you'll see in your logs is this:
// Note that the following sql`` structure is correct
SQL {
queryChunks: [
StringChunk { value: [ 'SELECT public.insert_vote(' ] },
'018abb841f4ae4257adbba232b34f848',
StringChunk { value: [ '::uuid, ' ] },
'018abb841f4ae4257adbba232b34f848',
StringChunk { value: [ '::uuid, ' ] },
[ '018abb841f4ae4257adbba232b34f848' ],
StringChunk { value: [ '::uuid[]);' ] }
],
decoder: { mapFromDriverValue: [Function: mapFromDriverValue] },
shouldInlineParams: false
}
// However, this isn't, as the 3rd parameter becomes a non-array.
Query: SELECT public.insert_vote($1, $2, ($3)); -- params: ["018abb841f4ae4257adbba232b34f848", "018abb841f4ae4257adbba232b34f848, "018abb841f4ae4257adbba232b34f848"]
PostgresError: malformed array literal: "018ac05623d3b7dec2b69bb78c8e0844"
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 weird record behavior by just adding another uuid into the array.
// ...
const choiceIds = ["018abb841f4ae4257adbba232b34f848", "018abb841f4ae4257adbba232b34f848"];
// ...
// This is using inlineParams() for when I was testing. Error is the same for either-or.
Query: SELECT public.insert_vote('018abb841f4ae4257adbba232b34f848'::uuid, '018abb841f4ae4257adbba232b34f848'::uuid, ('018abb841f4ae4257adbba232b34f848', '018abb841f4ae4257adbba232b34f848')::uuid[]);
PostgresError: cannot cast type record to uuid[]
// Hope is that it would've turned into this:
// SELECT public.insert_vote(
// '018abb841f4ae4257adbba232b34f848'::uuid,
// '018abb841f4ae4257adbba232b34f848'::uuid,
// '{018abb841f4ae4257adbba232b34f848, 018abb841f4ae4257adbba232b34f848}'::uuid[]);
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:
import "dotenv/config";
import postgres from "postgres";
import { drizzle } from "drizzle-orm/postgres-js";
import { DefaultLogger, SQL, sql } from "drizzle-orm";
const client = postgres({
hostname: process.env.POSTGRES_HOST,
database: process.env.POSTGRES_DB,
username: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
port: process.env.POSTGRES_PORT ? Number(process.env.POSTGRES_PORT) : 5432,
});
const db = drizzle(client, { logger: new DefaultLogger() });
const pollHexId = "018abb841f4ae4257adbba232b34f848";
const userId = "018abb841f4ae4257adbba232b34f848";
const choiceIds = [
"018abb841f4ae4257adbba232b34f848",
"018abb841f4ae4257adbba232b34f848",
];
const sqlQuery =
sql`SELECT public.insert_vote(${pollHexId}::uuid, ${userId}::uuid, `.inlineParams();
// Main code starts here
const sanitizedChoiceIds: SQL<unknown>[] = [sql`'{`];
const rawChoiceIds: SQL<unknown>[] = [];
for (const choiceId of choiceIds) {
// Sanitization happens with sql`${choiceId}`
const sanitizedChoiceIdChunk = sql`${choiceId}`.queryChunks[1];
// queryChunks = [
// StringChunk{ value: [ '' ] },
// '018abb841f4ae4257adbba232b34f848',
// 'StringChunk { value: [ '' ] }
// ]
rawChoiceIds.push(sql.raw(sanitizedChoiceIdChunk!.toString()));
}
sanitizedChoiceIds.push(sql.join(rawChoiceIds, sql`, `));
sanitizedChoiceIds.push(sql`}'::uuid[]`);
const sanitizedChoiceIdsQuery = sql.join(sanitizedChoiceIds);
sqlQuery.append(sanitizedChoiceIdsQuery);
sqlQuery.append(sql`)`);
// ...and now we have our query!
console.log((await import("util")).inspect(sqlQuery, undefined, 8));
const insertVote = await db.execute(sqlQuery);
console.log((await import("util")).inspect(insertVote, undefined, 4));
console.log("Finished!");
This will finally output the right query:
// Ignoring the extremely long SQL query object inspect...
Query: SELECT public.insert_vote('018abb841f4ae4257adbba232b34f848'::uuid, '018abb841f4ae4257adbba232b34f848'::uuid, '{018abb841f4ae4257adbba232b34f848, 018abb841f4ae4257adbba232b34f848}'::uuid[])
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.
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
recordtype if our array has more than one element.You can get the weird
recordbehavior by just adding anotheruuidinto 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.