extension-read-replicas icon indicating copy to clipboard operation
extension-read-replicas copied to clipboard

Fluent API returns incorrect object structure with read replica

Open snake575 opened this issue 2 years ago • 12 comments
trafficstars

When using Prisma's Fluent API with a read replica, the returned object does not adhere to the expected structure.

Repro: read-replicas-demo

Current Behavior:

Using the primary database, the following code:

await prisma.$primary().post.findFirst().author()

returns:

{ id: 1, email: '[email protected]', name: null }

However, when using a read replica, this code:

await prisma.post.findFirst().author() 

returns a nested object:

{ author: { id: 1, email: '[email protected]', name: null } }

Expected Behavior

Using a read replica should also return:

{ id: 1, email: '[email protected]', name: null }

snake575 avatar Sep 16 '23 04:09 snake575

This seems to be a deeper problem in client extensions, since I've encountered this when running my own client extensions.

casey-chow avatar Sep 21 '23 21:09 casey-chow

This issue seems to be specific to this extension needing to reconstruct a method call to the replica:

https://github.com/prisma/extension-read-replicas/blob/3c1a7127b4c80a3352893d8bea71c73898d8d391/src/extension.ts#L72-L76

The fluent API gets translated into operation: 'findFirst', args: { select: { author: true } }, so the reconstructed call has the result incorrectly nested. Normally, the query function seems to handle the work of lifting the nested data.

Within __internalParams, there appears to be a __internalParams.dataPath array which points to the nested path to extract, which maybe could be the solution but is a private API.

zacharyliu avatar Oct 02 '23 21:10 zacharyliu

I am having the same exact issue.

This extension cannot be used on projects using the fluent API.

tbell511 avatar Oct 22 '23 17:10 tbell511

@SevInf do you know when this might get fixed? 🙂

tbell511 avatar Oct 23 '23 19:10 tbell511

Sorry, can not provide ETA at the moment. Query extensions seem to have a problem with Fluent API in general and we probably should fix it in Prisma Client rather than work around in extension.

SevInf avatar Oct 25 '23 13:10 SevInf

Do we have a higher level issue for this @SevInf that we can link to?

janpio avatar Oct 25 '23 18:10 janpio

Tried adding a replica and ran into this same issue.

daniel-nagy avatar Jan 24 '24 16:01 daniel-nagy

Are there any updates to this? This extension is still completely useable for applications that use the fluent API.

tbell511 avatar Feb 23 '24 19:02 tbell511

I'd love to see this fixed too. This is such a great extension, but we can't use it until it supports the fluent API.

pedrovanzella avatar Apr 16 '24 15:04 pedrovanzella

We are using the fluent API and really want to see this land as well.

FWIW we slightly modified the $allOperations function similar to what @zacharyliu mentioned above that seems to work for our use case. Obviously not ideal.

async $allOperations({
  // @ts-expect-error __internalParams does not appear on the type as it's an internal property
  __internalParams: { dataPath, transaction },
  args,
  model,
  operation,
  query,
}) {
  if (transaction) return query(args);

  if (readOperations.includes(operation)) {
    const replica = replicaManager.pickReplica() as unknown as {
      [key: string]: {
        [key: string]: (args: unknown) => Promise<unknown>;
      };
    };

    if (
      typeof replica === "object" &&
      replica !== null &&
      model &&
      model in replica
    ) {
      const modelMethod = replica[model];

      if (modelMethod && operation in modelMethod) {
        const readMethod = modelMethod[operation];

        if (readMethod) {
          const res = (await readMethod(args)) as {
            [key: string]: unknown;
          };

          // This is a workaround for the Fluent API.
          // This references the internal dataPath to access the correct
          // nested property of the result.
          const path = dataPath as string[];

          if (path && path.length > 1) {
            const p = path[1];
            if (p && p in res) return res[p];
          }

          return res;
        }
      }
    }
  }

  return query(args);
}

jadenlemmon avatar Apr 16 '24 16:04 jadenlemmon

I put up a PR trying to productionize @jadenlemmon, though of course I'm not able to eliminate the fundamental jankiness of using (more) internal APIs: https://github.com/prisma/extension-read-replicas/pull/36

casey-chow avatar Jul 01 '24 17:07 casey-chow

Has anyone found a workaround for this?

baotpham avatar Sep 16 '24 10:09 baotpham