drizzle-graphql icon indicating copy to clipboard operation
drizzle-graphql copied to clipboard

impossible to define array of objects (aka jsonb) + no support for custom fields

Open creotip opened this issue 1 year ago • 7 comments

Seems like there is no easy way to define a field as array of objects. one way is to define with jsonb:

interface PreviousOwner {
  name: string
}

export const owners = pgTable('owners', {
  id: serial('id').primaryKey(),
  previousOwners: jsonb('previous_owners')
    .$type<PreviousOwner[]>()
    .notNull()
    .default([]),
})

Checking Apollo sandbox Introspection Schema. drizzle-graphql generates the following SDL schema:

input OwnersInsertInput {
  id: Int

  """JSON"""
  previousOwners: String
}

So, the mutation will accept String only:

mutation InsertIntoOwnersSingle {
  insertIntoOwnersSingle(
    values: { 
        previousOwners: "[{\"name\": \"jon doe3\"}]"
      }
  ) {
    id
    previousOwners
  }
}

Checking in drizzle studio. the record has been created: Screenshot 2024-09-16 at 12 42 33

Now, lets execute the query:

query Owners {
  owners {
    id
    previousOwners
  }
}

And here is the error:

{
  "errors": [
    {
      "message": "String cannot represent value: [\"{\\\"name\\\":\\\"jon doe3\\\"}\"]",
      "locations": [
        {
          "line": 4,
          "column": 5
        }
      ],
      "path": [
        "owners",
        0,
        "previousOwners"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "stacktrace": [
          "GraphQLError: String cannot represent value: [\"{\\\"name\\\":\\\"jon doe3\\\"}\"]",
          "    at GraphQLScalarType.serialize (/Users/myuser/Desktop/projects/my-turborepo/node_modules/.pnpm/[email protected]/node_modules/graphql/type/scalars.js:202:11)",
          "    at completeLeafValue (/Users/myuser/Desktop/projects/my-turborepo/node_modules/.pnpm/[email protected]/node_modules/graphql/execution/execute.js:749:39)",
          "    at completeValue (/Users/myuser/Desktop/projects/my-turborepo/node_modules/.pnpm/[email protected]/node_modules/graphql/execution/execute.js:630:12)",
          "    at completeValue (/Users/myuser/Desktop/projects/my-turborepo/node_modules/.pnpm/[email protected]/node_modules/graphql/execution/execute.js:595:23)",
          "    at executeField (/Users/myuser/Desktop/projects/my-turborepo/node_modules/.pnpm/[email protected]/node_modules/graphql/execution/execute.js:500:19)",
          "    at executeFields (/Users/myuser/Desktop/projects/my-turborepo/node_modules/.pnpm/[email protected]/node_modules/graphql/execution/execute.js:414:22)",
          "    at completeObjectValue (/Users/myuser/Desktop/projects/my-turborepo/node_modules/.pnpm/[email protected]/node_modules/graphql/execution/execute.js:925:10)",
          "    at completeValue (/Users/myuser/Desktop/projects/my-turborepo/node_modules/.pnpm/[email protected]/node_modules/graphql/execution/execute.js:646:12)",
          "    at completeValue (/Users/myuser/Desktop/projects/my-turborepo/node_modules/.pnpm/[email protected]/node_modules/graphql/execution/execute.js:595:23)",
          "    at /Users/myuser/Desktop/projects/my-turborepo/node_modules/.pnpm/[email protected]/node_modules/graphql/execution/execute.js:707:25"
        ]
      }
    }
  ],
  "data": null
}

Tried to fix it with custom field:

const customJsonB = customType<{
  data: PreviousOwner[]
}>({
  dataType: () => 'jsonb',
  fromDriver: (val) => JSON.parse(val as string),
  toDriver: (val: PreviousOwner[]) => JSON.stringify(val),
})

But there is no support for custom field:

throw new Error(`Drizzle-GraphQL Error: Type ${column.dataType} is not implemented!`);

https://github.com/drizzle-team/drizzle-graphql/blob/c558249fadcc7bd1af8ec17faf292d8cf2d365c5/src/util/type-converter/index.ts#L121

creotip avatar Sep 16 '24 09:09 creotip

I'm also stuck with no support for custom fields. In my case I'm trying to simply define some postgres like types, such as:

export const tstzrange = customType<TsTzRange>({
  dataType() {
    return "tstzrange";
  },
  toDriver(value: TsTzRange["data"]): string {
    return `[${value.from},${value.to})`;
  },
  fromDriver(value: string): TsTzRange["data"] {
    const [from, to] = value.slice(1, -1).split(",");
    return {
      from,
      to,
    };
  },
});

but I get

/node_modules/src/util/type-converter/index.ts:121
                        throw new Error(`Drizzle-GraphQL Error: Type ${column.dataType} is not implemented!`);
                              ^


Error: Drizzle-GraphQL Error: Type custom is not implemented!

Is it something I'm doing wrong or simply there's still not a support for this use case? Any idea how to move forward?

chemalopezp avatar Oct 07 '24 09:10 chemalopezp

@chemalopezp I forked the repo and added support for array of objects and partial support for custom fields. Will push PR soon.

ruslan-primesec avatar Oct 07 '24 13:10 ruslan-primesec

@ruslan-primesec that's amazing! Thank you so much, let me know if I can be of any help 🎉 Looking forward to test it ;)

chemalopezp avatar Oct 07 '24 20:10 chemalopezp

@ruslan-primesec I hope everything is going well! Is there any way I could help with the custom fields PR? Or do you already have a timeline? Thank you so much!

chemalopezp avatar Oct 17 '24 15:10 chemalopezp

I'm also interested in support for custom types. I have a SHA256 field like:

import { sql } from "drizzle-orm";
import { customType } from "drizzle-orm/mysql-core";

export const sha256 = customType<{
	data: Uint8Array;
	notNull: true;
	driverData: string;
}>({
	dataType() {
		return "binary(32)";
	},
	fromDriver(value): Uint8Array {
		return typeof value === "string"
			? new Uint8Array(Array.from(value).map((char) => char.charCodeAt(0)))
			: new Uint8Array(value as ArrayBufferLike);
	},
	toDriver(value) {
		return sql`UNHEX(SHA2(${value}, 256))`;
	},
});

and it cannot be used with the generator due to it throwing on the custom type

chocolatkey avatar Nov 22 '24 01:11 chocolatkey

@ruslan-primesec The issue with implementing custom is that the underlying datatype is hidden. Right now, I have hack (below) but the right behavior would be to add a field like underlyingType to Column.

index 5225f1d..eb3c7d1 100644
--- a/src/util/type-converter/index.ts
+++ b/src/util/type-converter/index.ts
@@ -55,13 +55,20 @@ const geoXyInputType = new GraphQLInputObjectType({
        },
 });

+const getColumnDataType = (column: Column) => {
+       if (column.dataType !== 'custom') { return  column.dataType};
+       return typeof column.mapFromDriverValue('');
+
+}
+
 const columnToGraphQLCore = (
        column: Column,
        columnName: string,
        tableName: string,
        isInput: boolean,
 ): ConvertedColumn<boolean> => {
-       switch (column.dataType) {
+       const dataType = getColumnDataType(column);
+       switch (dataType) {
                case 'boolean':
                        return { type: GraphQLBoolean, description: 'Boolean' };
                case 'json':
@@ -116,9 +123,8 @@ const columnToGraphQLCore = (
                                description: `Array<${innerType.description}>`,
                        };
                }
-               case 'custom':
                default:
-                       throw new Error(`Drizzle-GraphQL Error: Type ${column.dataType} is not implemented!`);
+                       throw new Error(`Drizzle-GraphQL Error: Type ${dataType} is not implemented!`);
        }
 };

Malvolio avatar Jan 04 '25 19:01 Malvolio

+1 on fixing this, we're currently blocked on adding a custom scalar for storing polygons in postgres

mschinis avatar Feb 05 '25 12:02 mschinis