ampp
ampp6mo ago

Pagination, Take on a edge [ents]

I've been browsing and trying to figure out how to handle this for a few days. Basically i have a many to many between wallets and transactions. The only efficient way to get the entire transaction history is to do something like .table('wallets') .getX(wallet._id) .edge('transactions') But i cant do a pagination or a take or anything on that on a edge... so i assume anything i do is going to be a full read of all results. And can be thousands of transactions. The problem is that transactions has a array of to's and a array of from's with a walletid/amount pairing. Which would be bad to index and we want to keep transactions as small as possible. If i'm not missing something painfully simple, then i'm thinking directly accessing the linking (wallet_to_transaction) table would be smarter. Perhaps then i could do manual pagination or something, or at least get arrays of ids to use? Thanks
53 Replies
Michal Srb
Michal Srb6mo ago
GitHub
many:many edge cannot be paginated · Issue #25 · xixixao/convex-ents
This const result = await ctx .table("messages") .getX(messageId) .edge("tags") .paginate(paginationOpts); should paginate the underlying edge table before loading the ents on t...
ampp
amppOP6mo ago
@Michal Srb i had seen that before. Back when i wasn't planning on many to manys. I haven't seen anything how to access the linking tables that ents generate from these relationships. But i also couldn't access the table from normal convex either. Is there a trick to get around the guard rails?
ian
ian6mo ago
You can do something like this: https://labs.convex.dev/convex-ents/setup/config#incremental-adoption-restrict-ctxdb Or to allow getting around it fully you can just not over-write db at all, and access it like ctx.db.
Configuring Functions - Convex Ents
Relations, default values, unique fields and more for Convex
ian
ian6mo ago
ampp
amppOP6mo ago
I am doing this by just accessing the functions directly as its working with all my normal tables, its just like ctx.db is unaware the generated ents table exists. I'll try the skip version as i got that too. I guess it was more of a typescirpt error with native convex, but is any the solution? await ctx.db ?.query('users_to_members' as any) .collect()
ian
ian6mo ago
If you don’t set db to anything it should have types for all the tables. Only if you do the Pick version should it only select a few tables, and it should be defined in the query/mutation for the custom function… not sure why that wouldn’t work. Just don’t cast db to DataModel or anything, it should have the types already- and hopefully within the custom function you could see it autocomplete with db.query(“
Michal Srb
Michal Srb6mo ago
@ampp Yes, ctx.db will not have the type for the many:many edge table, and as any is a fine workaround.
ampp
amppOP5mo ago
Assuming pagination worked on a many to many from what i understand it would never support proper sorting as the linking table has a walletId_transactionId index and a transactionId_walletId index. So i can't get the last 10 transactions(and paginate) here either. Wouldn't this never be possible to implement with the current design constraints?
Michal Srb
Michal Srb5mo ago
Last 10 transaction globally or with one of the IDs? Either way you can get it: - Globally: Use the default _creationTime index (you have to do this manually via ctx.db right now) - For some id: Use the relevant-direction index (you can use the Ents edge method)
ampp
amppOP5mo ago
I feel totally lost here, as long as i'm searching using a index i cant sort by creationTime?
ent Crated Table:
wallets_to_walletTransactions: defineTable({
walletTransactionsId: v.id(
"walletTransactions"
),
walletsId: v.id("wallets"),
})
.index("walletTransactionsId", [
"walletTransactionsId",
"walletsId",
])
.index("walletsId", [
"walletsId",
"walletTransactionsId",
]),
ent Crated Table:
wallets_to_walletTransactions: defineTable({
walletTransactionsId: v.id(
"walletTransactions"
),
walletsId: v.id("wallets"),
})
.index("walletTransactionsId", [
"walletTransactionsId",
"walletsId",
])
.index("walletsId", [
"walletsId",
"walletTransactionsId",
]),
ctx.table('wallets').get(wid).edge('walletTransactions') will return a ordered by _creationTime. granted all results. because i cant paginate or take. as soon as i do this:
ctx.table(
'wallets_to_walletTransactions' as any,
'walletsId',
(q) => q.eq('walletsId', args.walletId)
)
.order('desc')
.paginate(args.paginationOpts)
ctx.table(
'wallets_to_walletTransactions' as any,
'walletsId',
(q) => q.eq('walletsId', args.walletId)
)
.order('desc')
.paginate(args.paginationOpts)
or
const transactions = await ctx.db
.query('wallets_to_walletTransactions' as any)
.withIndex('walletsId', (q) =>
q.eq('walletsId', args.walletId)
)
.order('desc')
.paginate(args.paginationOpts)
const transactions = await ctx.db
.query('wallets_to_walletTransactions' as any)
.withIndex('walletsId', (q) =>
q.eq('walletsId', args.walletId)
)
.order('desc')
.paginate(args.paginationOpts)
its unsorted. well first sorted by wallet then transaction then by _creationTime, how can i bypass that?
Michal Srb
Michal Srb5mo ago
Ok two things that needs fixing I think: 1. Adding paginate: https://github.com/xixixao/convex-ents/issues/25 2. (this one is harder to work around) Right now there are two indexes, for both directions, but one of them has the other field as well for point lookup. But we probably want 3 indexes, one for each direction and one for lookup. And you're probably hitting the 2nd case, right?
GitHub
many:many edge cannot be paginated · Issue #25 · xixixao/convex-ents
This const result = await ctx .table("messages") .getX(messageId) .edge("tags") .paginate(paginationOpts); should paginate the underlying edge table before loading the ents on t...
ampp
amppOP5mo ago
Yeah #2 for sure. I think in my case i care less about traversing from walletTransactions to wallets as i have two arrays in my transactions table of from and to wallets.
Michal Srb
Michal Srb5mo ago
If you swap the order of tables or edge declarations, you might coax the library to declare the other direction index to be the compound one. Either way I added it to the paginate issue to fix this.
ampp
amppOP5mo ago
Id be happy with the partial fix of adding the index to the linking table. As far as i understand this is not something i can do. For now, I think following a many to many relationship should come with a cavate that it will blow up at as soon as the relationship has 4096 records if its left without pagination/take ability .table('wallets') .getX(wallet._id) .edge('walletTransactions')
Michal Srb
Michal Srb5mo ago
@ampp both issues fixed, upgrade to 0.9.0 Also let me know if there are any other issues that are not tracked on GitHub
ampp
amppOP5mo ago
Awesome, Thanks for the attention on this. I am getting a error with 0.9.0 on at least one of my many to many:
Error fetching POST https://hidden-site-url.convex.cloud/api/prepare_schema 400 Bad Request: InvalidIndexName: Hit an error while evaluating your schema:
In table "wallet_to_walletTransactions": Invalid index name: "walletsId-walletTransactionsId". Identifiers must be 64 characters or less, start with a letter, and only contain letters, digits, underscores.
Error fetching POST https://hidden-site-url.convex.cloud/api/prepare_schema 400 Bad Request: InvalidIndexName: Hit an error while evaluating your schema:
In table "wallet_to_walletTransactions": Invalid index name: "walletsId-walletTransactionsId". Identifiers must be 64 characters or less, start with a letter, and only contain letters, digits, underscores.
Michal Srb
Michal Srb5mo ago
/facepalm
ampp
amppOP5mo ago
🙂 it happens lol
Michal Srb
Michal Srb5mo ago
fixed 0.9.1
ampp
amppOP5mo ago
Nice, this week seems especially /facepalm for me, the most annoying one spending several hours trying to get a friends environment up for the 2nd time and ran out of time again. couldn't get any queries to load.
Michal Srb
Michal Srb5mo ago
On Windows?
ampp
amppOP5mo ago
Yeah, with wsl mutations worked however..
Michal Srb
Michal Srb5mo ago
Maybe auth issue? Seems unlikely mutations would work but queries wouldn't
ampp
amppOP5mo ago
now im getting 400 Bad Request: IndexNotUnique: Indexes must be unique within a table. with 0.9.1 Yeah i tried everything, outside making a new clerk account redid stuff and reviewed things 4 times. made a new convex project too. we've probably done 10 clean slate setups and its only been this one that is trouble. Even queries that run with no login required were blank
Michal Srb
Michal Srb5mo ago
Third time's the charm: convex-ents@0.9.2
ampp
amppOP5mo ago
Yes that did the trick! 20 new indexes 😅 Updated* site is broken.. im investigating this
Error: [CONVEX Q(list:joinedList)] [Request ID: b5dff3ef5562c48a] Server Error
Uncaught TypeError: Cannot read properties of undefined (reading 'cardinality')
at edgeImpl [as edgeImpl] (../../../../node_modules/.pnpm/convex-ents@0.9.2_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_mefz6wz7rmvjqzvzpcz3qgo4hu/node_modules/convex-ents/src/functions.ts:1694:10)
at edge [as edge] (../../../../node_modules/.pnpm/convex-ents@0.9.2_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_mefz6wz7rmvjqzvzpcz3qgo4hu/node_modules/convex-ents/src/functions.ts:1679:2)
at value (../../../../node_modules/.pnpm/convex-ents@0.9.2_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_mefz6wz7rmvjqzvzpcz3qgo4hu/node_modules/convex-ents/src/functions.ts:1858:4)
at <anonymous> (../../convex/spheres/list.ts:46:15)
at map [as map] (<anonymous>)
at Promise.retrieve (../../../../node_modules/.pnpm/convex-ents@0.9.2_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_mefz6wz7rmvjqzvzpcz3qgo4hu/node_modules/convex-ents/src/functions.ts:977:5)
at async handler [as handler] (../../convex/spheres/list.ts:39:29)
at async handler (../../../../node_modules/.pnpm/convex-helpers@0.1.49_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__r_ozihjcfdp7bua7a3x7pbwydnum/node_modules/convex-helpers/server/customFunctions.js:89:16)
Error: [CONVEX Q(list:joinedList)] [Request ID: b5dff3ef5562c48a] Server Error
Uncaught TypeError: Cannot read properties of undefined (reading 'cardinality')
at edgeImpl [as edgeImpl] (../../../../node_modules/.pnpm/convex-ents@0.9.2_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_mefz6wz7rmvjqzvzpcz3qgo4hu/node_modules/convex-ents/src/functions.ts:1694:10)
at edge [as edge] (../../../../node_modules/.pnpm/convex-ents@0.9.2_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_mefz6wz7rmvjqzvzpcz3qgo4hu/node_modules/convex-ents/src/functions.ts:1679:2)
at value (../../../../node_modules/.pnpm/convex-ents@0.9.2_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_mefz6wz7rmvjqzvzpcz3qgo4hu/node_modules/convex-ents/src/functions.ts:1858:4)
at <anonymous> (../../convex/spheres/list.ts:46:15)
at map [as map] (<anonymous>)
at Promise.retrieve (../../../../node_modules/.pnpm/convex-ents@0.9.2_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_mefz6wz7rmvjqzvzpcz3qgo4hu/node_modules/convex-ents/src/functions.ts:977:5)
at async handler [as handler] (../../convex/spheres/list.ts:39:29)
at async handler (../../../../node_modules/.pnpm/convex-helpers@0.1.49_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__r_ozihjcfdp7bua7a3x7pbwydnum/node_modules/convex-helpers/server/customFunctions.js:89:16)
its on a many to 1 i think
Michal Srb
Michal Srb5mo ago
It's a call to .edge or .edgeX, would be helpful to know which edge and what your schema looks like
ampp
amppOP5mo ago
something with
const members = await user
.edge('members')
.map(async (member) => {
const organization = await member.edge('organization')
const members = await user
.edge('members')
.map(async (member) => {
const organization = await member.edge('organization')
Michal Srb
Michal Srb5mo ago
And your schema for members?
ampp
amppOP5mo ago
users to members is a many to many
Michal Srb
Michal Srb5mo ago
And members to organization is?
ampp
amppOP5mo ago
members.edges('users').edge("orgs")
Michal Srb
Michal Srb5mo ago
I got a repro, sec
ampp
amppOP5mo ago
my intent is to go through and optimize these queries, i guess now i do have the ability to be more specific with the new updates I think members to users is actually our only case where we transverse the many to many in both directions right now for the fun of it 🙃 . A user can have many members within one org, and many orgs tied to the members. But users table is not aware of orgs. It will be a busy lookup.
Michal Srb
Michal Srb5mo ago
convex-ents@0.9.3 (that was a bug I just introduced when implementing the pagination) Getting greatly improved test coverage from all these 🙂
ampp
amppOP5mo ago
Yeah, so far so good, i got to click on a few more pages for all the variations as our tests aren't testing most queries well this one is harder to track down, still trying to figure it out
Uncaught TypeError: Cannot read properties of null (reading 'orgId')
at Promise.retrieve (../../../../node_modules/.pnpm/convex-ents@0.9.3_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_7izxzvnpaivdxggmdgwfna2ns4/node_modules/convex-ents/src/functions.ts:1764:15)
at async Promise.retrieve (../../../../node_modules/.pnpm/convex-ents@0.9.3_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_7izxzvnpaivdxggmdgwfna2ns4/node_modules/convex-ents/src/functions.ts:1742:25)
at async doc [as doc] (../../../../node_modules/.pnpm/convex-ents@0.9.3_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_7izxzvnpaivdxggmdgwfna2ns4/node_modules/convex-ents/src/functions.ts:1621:21) ...
Uncaught TypeError: Cannot read properties of null (reading 'orgId')
at Promise.retrieve (../../../../node_modules/.pnpm/convex-ents@0.9.3_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_7izxzvnpaivdxggmdgwfna2ns4/node_modules/convex-ents/src/functions.ts:1764:15)
at async Promise.retrieve (../../../../node_modules/.pnpm/convex-ents@0.9.3_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_7izxzvnpaivdxggmdgwfna2ns4/node_modules/convex-ents/src/functions.ts:1742:25)
at async doc [as doc] (../../../../node_modules/.pnpm/convex-ents@0.9.3_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_7izxzvnpaivdxggmdgwfna2ns4/node_modules/convex-ents/src/functions.ts:1621:21) ...
Michal Srb
Michal Srb5mo ago
Looks like a dangling reference that isn't correctly checked for Is that the complete stack trace? There should be a line that includes your code (as opposed to the library code paths)
ampp
amppOP5mo ago
Yeah i cut off some of it, it might just be my problem, i got to wipe everything, i deleted something that left over data that didnt get a cascade if i delete a member right now its a problem
Michal Srb
Michal Srb5mo ago
Yeah still there should be a better error message So I'd love to know exactly the scenario (the rest of the stack trace would be helpful)
ampp
amppOP5mo ago
im reloading everything, it was referencing a document that was not in my db. It just seemed odd well my init is throwing this error
init:insertMemberRolesAndPermissions
failure
929ms
Error: [Request ID: f68703a80e0b5cfc] Server Error
Uncaught Error: Uncaught Error: The index range included a comparison with "memberPermissionsId", but memberRoleDefinitions_to_memberPermissions.memberRoleDefinitionsId with fields ["memberRoleDefinitionsId", "_creationTime"] doesn't index this field. For more information see https://docs.convex.dev/using/indexes.
at <anonymous> (../../../../node_modules/.pnpm/convex-ents@0.9.3_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_7izxzvnpaivdxggmdgwfna2ns4/node_modules/convex-ents/src/writer.ts:220:18)
at map [as map] (<anonymous>)
at <anonymous> (../../../../node_modules/.pnpm/convex-ents@0.9.3_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_7izxzvnpaivdxggmdgwfna2ns4/node_modules/convex-ents/src/writer.ts:211:24)
at map [as map] (<anonymous>)

at async handler (../../convex/init.ts:95:20)
init:insertMemberRolesAndPermissions
failure
929ms
Error: [Request ID: f68703a80e0b5cfc] Server Error
Uncaught Error: Uncaught Error: The index range included a comparison with "memberPermissionsId", but memberRoleDefinitions_to_memberPermissions.memberRoleDefinitionsId with fields ["memberRoleDefinitionsId", "_creationTime"] doesn't index this field. For more information see https://docs.convex.dev/using/indexes.
at <anonymous> (../../../../node_modules/.pnpm/convex-ents@0.9.3_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_7izxzvnpaivdxggmdgwfna2ns4/node_modules/convex-ents/src/writer.ts:220:18)
at map [as map] (<anonymous>)
at <anonymous> (../../../../node_modules/.pnpm/convex-ents@0.9.3_convex@1.13.2_@clerk+clerk-react@5.3.1_react-dom@18.3.1_react@18.3.1__react_7izxzvnpaivdxggmdgwfna2ns4/node_modules/convex-ents/src/writer.ts:211:24)
at map [as map] (<anonymous>)

at async handler (../../convex/init.ts:95:20)
memberRoleDefinitions: defineEnt({
isDefault: v.boolean(),
})
.field('name', vRole, { unique: true })
.edges('memberPermissions')
.edges('memberRoleAssignments', { ref: true })

memberPermissions: defineEnt({})
.field('name', vMemberPermission, { unique: true })
.edge('memberSpecificPermission', { optional: true })
.edges('memberRoleDefinitions'),
memberRoleDefinitions: defineEnt({
isDefault: v.boolean(),
})
.field('name', vRole, { unique: true })
.edges('memberPermissions')
.edges('memberRoleAssignments', { ref: true })

memberPermissions: defineEnt({})
.field('name', vMemberPermission, { unique: true })
.edge('memberSpecificPermission', { optional: true })
.edges('memberRoleDefinitions'),
our members have many roles with many permissions so its also crazy 😅
Michal Srb
Michal Srb5mo ago
Ah, see it. convex-ents@0.9.4
ampp
amppOP5mo ago
Ok yeah, i just noticed the same one in the users to members relationship, and it appears fixed with 0.9.4 going to init again to see for sure ya this one was my issue, im fairly sure.
Michal Srb
Michal Srb5mo ago
If you have the full stack trace, would still be helpful to see it. The error message should say there's a dangling reference or throw earlier.
ampp
amppOP5mo ago
I would have to dig through the logs. im confident it was a dangling reference interesting, there may be a query issue with rendering the list off a edges, we have the ability to send a transaction to yourself (dont ask 😅 ), i may have to deal with it differently as it creates two transactions. Anyway, after i add this self transaction then when i send other transactions it populates the list that self transaction a new row each time i send as well as a the new transaction. im investigating. ill first get rid of this extra transaction and see what happens after that I think i at least managed to confuse the code that pushes these updates, i have yet to turn on pagination here
ampp
amppOP5mo ago
easier to display it, while im on this page i hit send for 16 and 17 and that 500 grew from 2 to 4 results and if i refresh it the 2 extra entries aren't there.
No description
ampp
amppOP5mo ago
i don't think we want to use inverse on this table
Michal Srb
Michal Srb5mo ago
I think I'm off here. I don't really understand the issue from the description above.
ampp
amppOP5mo ago
No worries, thanks for everything. I'll post a update to this as i play with the new pagination and such. if it ends up being a thing or not
Michal Srb
Michal Srb5mo ago
Thank you!
ampp
amppOP5mo ago
the problem is easy to resolve, just don't have duplicates in your manytomany linking table. in wallets_walletTransactions i have two entries with matching walletId and walletTransactionId. when that condition exists, every time you add something to those tables additional duplicate entry row is displayed on the UI for every duplicate entry shown. im testing this on non ents right now
Michal Srb
Michal Srb5mo ago
Yeah Ents should never create two such edges, but you got into that situation with the last bug With it fixed, you should not get into that situation again
ampp
amppOP5mo ago
const allWalletIds = [
...from.map((wallet) => wallet.id),
...to.map((wallet) => wallet.id),
]
await ctx.table('walletTransactions').insert({
wallets: allWalletIds, <--- its letting me put the same wallet id in here twice.
const allWalletIds = [
...from.map((wallet) => wallet.id),
...to.map((wallet) => wallet.id),
]
await ctx.table('walletTransactions').insert({
wallets: allWalletIds, <--- its letting me put the same wallet id in here twice.
i needed a check there anyway 😅
Michal Srb
Michal Srb5mo ago
convex-ents@0.9.5 There was another bug for symmetrical many:many edges. The pagination fix caused a lot of issues because the index usage is not strongly typed inside the library implementation (and convex-test wasn't enforcing many of the constraints the real backend did around schema and indexes - I fixed that too now).