Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support json_agg(column_ref) #1316

Open
wants to merge 3 commits into
base: v0.28
Choose a base branch
from

Conversation

SimonSimCity
Copy link

Fixes #1280.

Wow, my first PR in this repo 🤩

If there was something wrong about what I wrote - e.g. if the types are too narrow or wide, please let me know.

Here's what I know about this:

  • Postgresql returns an array if there are elements in the linked table.
  • Postgresql returns null otherwise.

According to the docs, this method does only exist for Postgresql, and I therefore didn't look into any other of the options.

Copy link

vercel bot commented Jan 9, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
kysely ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 13, 2025 8:30am

Copy link

pkg-pr-new bot commented Jan 9, 2025

Open in Stackblitzkysely_koa_example

npm i https://pkg.pr.new/kysely-org/kysely@1316

commit: 39976bb

@SimonSimCity
Copy link
Author

Since it's likely that people will use this functionality with coalesce, I thought of adding a helper function, similar to the existing jsonArrayFrom. I don't know how you have it in regards to other dialects, because I would only be able to add it for Postgres. Here's an example of how this could be used: #1280 (comment)

@igalklebanov igalklebanov changed the base branch from master to v0.28 January 12, 2025 18:05
@igalklebanov igalklebanov force-pushed the Support-json_agg(column_ref) branch from 6be68bc to 63ce1a6 Compare January 12, 2025 18:05
@igalklebanov igalklebanov added enhancement New feature or request postgres Related to PostgreSQL api Related to library's API typescript Related to Typescript labels Jan 12, 2025
Co-authored-by: Simon Schick <[email protected]>
@igalklebanov
Copy link
Member

igalklebanov commented Jan 12, 2025

Hey 👋

Thanks! 💪

Since it's likely that people will use this functionality with coalesce, I thought of adding a helper function, similar to the existing jsonArrayFrom. I don't know how you have it in regards to other dialects, because I would only be able to add it for Postgres. Here's an example of how this could be used: #1280 (comment)

I'll need to think about the helper. You're right about the usage pattern due to nullability.

  1. How would you name such a helper without making stuff confusing (due to the existence of jsonArrayFrom)?
  2. Can it be replicated with other dialects? people might get disappointed with other dialects not having something similar.
  3. Not that big of a lift compared to other helpers:
(eb) => eb.coalesce(eb.jsonAgg("col"), sql<string[]>`[]`)

vs.

import { someHelper } from 'kysely/helpers/postgres'

(eb) => someHelper(eb.ref("col"))

The existing helpers were born out of a LOT of signals from the community needing something after they ditched some ORM and did not know how to do nested stuff with SQL.

Regardless, this shouldn't be part of this PR.

@@ -696,6 +723,10 @@ export interface FunctionModule<DB, TB extends keyof DB> {
: never
>

jsonAgg<RE extends StringReference<DB, TB>>(
column: RE,
): RawBuilder<ExtractTypeFromStringReference<DB, TB, RE>[] | null>
Copy link
Member

@igalklebanov igalklebanov Jan 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is a RawBuilder returned here and not AggregateFunctionBuilder?

By not returning the latter you're stripping downstream capabilities.

Copy link
Member

@igalklebanov igalklebanov Jan 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g. this is valid SQL:

SELECT name,
  coalesce(json_agg(laptop.model order by laptop.model DESC) filter (where laptop.model != 'Core 2 Duo'), '[]') as models
from employee 
left join laptop on employee.empId = laptop.assignedTo 
group by employee.empId;

https://onecompiler.com/postgresql/435v97hnu

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's due to my inexperience with this library 😅 Thanks for pointing this out. Should I add a query like this to the tests as well?

Copy link
Member

@igalklebanov igalklebanov Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's due to my inexperience with this library 😅

When you're new to a codebase, it's perfectly fine to not get things right the first time.

I'm just asking questions to get a feel for your thought process here.

You either have a good reason for your choice or by asking, I could find something that could be improved, so the next time, it's clearer what the correct choice should be.

Should I add a query like this to the tests as well?

To runtime tests, maybe.

Comment on lines +186 to +204

const r6 = await db
.selectFrom('person')
.select((eb) => [
'first_name',
eb
.selectFrom('pet')
.select((eb) => [eb.fn.jsonAgg('pet.owner_id').as('pet_names')])
.whereRef('pet.owner_id', '=', 'person.id')
.as('pet_names'),
])
.execute()

expectType<
{
first_name: string
pet_names: number[] | null
}[]
>(r6)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one doesn't add much value, let's remove it.

Suggested change
const r6 = await db
.selectFrom('person')
.select((eb) => [
'first_name',
eb
.selectFrom('pet')
.select((eb) => [eb.fn.jsonAgg('pet.owner_id').as('pet_names')])
.whereRef('pet.owner_id', '=', 'person.id')
.as('pet_names'),
])
.execute()
expectType<
{
first_name: string
pet_names: number[] | null
}[]
>(r6)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be that this typing-wise doesn't make a difference, but to me this is another use-case. It's using the json-agg in a subquery rather than in a joined table. In a joined table you need a groupBy, but in a subquery you don't.

Copy link
Member

@igalklebanov igalklebanov Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typings tests are there to ensure we got things right on the type-level. Not to document the SQL spec.

The relationship between a root query and its sub-queries has been thoroughly tested elsewhere.
groupBy doesn't affect anything on the type-level. This test case doesn't add any value here.

Excessive tests mean:

  1. more stuff to maintain.
  2. more stuff to wait for locally or in CI.

@igalklebanov
Copy link
Member

This could really use a unit test.

@SimonSimCity
Copy link
Author

SimonSimCity commented Jan 13, 2025

Could you assist me in writing a unit test? What unit do you think should be tested though not to make it an integration test?

@igalklebanov
Copy link
Member

Could you assist me in writing a unit test? What unit do you think should be tested though not to make it an integration test?

Sure. We could also pair up on this if you want (DM on Discord).

We have a suite for aggregate functions, probably there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api Related to library's API enhancement New feature or request postgres Related to PostgreSQL typescript Related to Typescript
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support json_agg(column_ref).
2 participants