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

[feature] default values for fields #975

Open
Sofianel5 opened this issue Jul 17, 2024 · 2 comments
Open

[feature] default values for fields #975

Sofianel5 opened this issue Jul 17, 2024 · 2 comments

Comments

@Sofianel5
Copy link

Databases and ORMs generally allow fields to be defined with a default value. Doing this now in ponder is a little janky. For example, I have a schema where I want to keep track of points users accrue (all users start at 0).

Current Implementation

In order to implement this right now, I need to either explicitly set points to 0 whenever I create a new user or set points to be an optional value. The former would be extremely tedious as I create new accounts in many different watchers, and its much cleaner to just do

await Account.upsert({
    id: event.args.from,
});

then explicitly set points to 0 across all watchers that create accounts but have nothing to do with points. However the latter requires consumers of the indexer to explicitly cast null to 0 on the points field of accounts. Currently, my has to do this.

// schema.ponder.ts
export default createSchema((p) => ({
  Account: p.createTable({
    id: p.hex(),
    tokens: p.many("TokenBalance.ownerId"),
    points: p.bigint().optional(),
    transferFromEvents: p.many("TransferEvent.fromId"),
    transferToEvents: p.many("TransferEvent.toId"),
  }),
}));

// Contract.ts
ponder.on("Contract:PointEarned", async ({ event, context }) => {
  const { Account } = context.db;

  await Account.upsert({
    id: event.args.user,
    update: ({ current }) => ({
      points: (current.points ?? 0n) + 1n,
    }),
  });
});

Usage Example

Here's an example of what this might look like in ponder.schema.ts with the requested feature.

import { createSchema } from "@ponder/core";

export default createSchema((p) => ({
  Account: p.createTable({
    id: p.hex(),
    tokens: p.many("TokenBalance.ownerId"),
    points: p.bigint(0n), // Set a default value of 0 for points
    transferFromEvents: p.many("TransferEvent.fromId"),
    transferToEvents: p.many("TransferEvent.toId"),
  }),
}));

Then I don't need to change anything about my indexer code except increment the points counter.

ponder.on("Contract:PointEarned", async ({ event, context }) => {
  const { Account } = context.db;

  await Account.upsert({
    id: event.args.user,
    update: ({ current }) => ({
      points: current.points + 1n,
    }),
  });
});
@0xOlias
Copy link
Collaborator

0xOlias commented Jul 22, 2024

Great idea, this would be a nice quality of life improvement. I propose a slightly different API:

import { createSchema } from "@ponder/core";

export default createSchema((p) => ({
  Account: p.createTable({
    id: p.hex(),
    tokens: p.many("TokenBalance.ownerId"),
    points: p.bigint().default(0n), // Set a default value of 0 for points
    // ...
  }),
}));

which would align better with the existing .optional() and .references(...) builder pattern we have.

Implementation-wise, we should be able to use a SQL DEFAULT for both SQLite and Postgres. At a type level, adding .default(...) would make the column optional on insert, but required on select.

Thoughts @Sofianel5 ?

@Sofianel5
Copy link
Author

My initial thought to just supply it as a value to the type function made more sense to me initially, but with the optional() and .references() builder pattern I'd agree that a .default(x) would fit better within the system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants