Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/adapter-mariadb/src/conversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { ArgType, ColumnType, ColumnTypeEnum, ResultValue } from '@prisma/driver
import * as mariadb from 'mariadb'

const UNSIGNED_FLAG = 1 << 5
const BINARY_FLAG = 1 << 7
// https://github.com/mariadb-corporation/mariadb-connector-nodejs/blob/be72ebf9fee6e0bd153b6ff6e0bb252f794dbf0e/lib/const/collations.js#L150
const BINARY_COLLATION_INDEX = 63

const enum MariaDbColumnType {
DECIMAL = 'DECIMAL',
Expand Down Expand Up @@ -83,7 +84,10 @@ export function mapColumnType(field: mariadb.FieldInfo): ColumnType {
// https://github.com/mariadb-corporation/mariadb-connector-nodejs/blob/1bbbb41e92d2123948c2322a4dbb5021026f2d05/lib/cmd/column-definition.js#L27
if (field['dataTypeFormat'] === 'json') {
return ColumnTypeEnum.Json
} else if (field.flags.valueOf() & BINARY_FLAG) {
}
// The Binary flag of column definition applies to both text and binary data. To distinguish them, check if collation == 'binary' instead of checking the flag.
// https://github.com/mariadb-corporation/mariadb-connector-nodejs/blob/be72ebf9fee6e0bd153b6ff6e0bb252f794dbf0e/lib/cmd/decoder/text-decoder.js#L44
else if (field.collation.index === BINARY_COLLATION_INDEX) {
return ColumnTypeEnum.Bytes
} else {
return ColumnTypeEnum.Text
Expand Down
22 changes: 17 additions & 5 deletions packages/adapter-planetscale/src/conversion.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { cast as defaultCast } from '@planetscale/database'
import { cast as defaultCast, type Field } from '@planetscale/database'
import { ArgType, type ColumnType, ColumnTypeEnum } from '@prisma/driver-adapter-utils'

import { decodeUtf8 } from './text'

// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_character_set.html
const BINARY_COLLATION_INDEX = 63

// See: https://github.com/planetscale/vitess-types/blob/06235e372d2050b4c0fff49972df8111e696c564/src/vitess/query/v16/query.proto#L108-L218
export type PlanetScaleColumnType =
type PlanetScaleColumnType =
| 'NULL'
| 'INT8'
| 'UINT8'
Expand Down Expand Up @@ -46,8 +49,9 @@ export type PlanetScaleColumnType =
* module to see how other attributes of the field packet such as the field length are used to infer
* the correct quaint::Value variant.
*/
export function fieldToColumnType(field: PlanetScaleColumnType): ColumnType {
switch (field) {
export function fieldToColumnType(field: Field): ColumnType {
const type = field.type as PlanetScaleColumnType
switch (type) {
case 'INT8':
case 'UINT8':
case 'INT16':
Expand Down Expand Up @@ -85,6 +89,14 @@ export function fieldToColumnType(field: PlanetScaleColumnType): ColumnType {
case 'BLOB':
case 'BINARY':
case 'VARBINARY':
// vitess converts CHAR/VARCHAR/TEXT columns with a binary collation to BINARY/VARBINARY/BLOB respectively before returning them to @planetscale/database driver.
// https://github.com/vitessio/vitess/blob/a94fa13f2ab53c98aad07a56eb15fe20b5ea7ade/go/sqltypes/type.go#L269
// Therefore, we check the collation to distinguish between text and binary data.
// https://github.com/planetscale/database-js/blob/de78eebfaec8cd88c670b8c644fc5a3fd69e664c/src/cast.ts#L92
if (field.charset && field.charset !== BINARY_COLLATION_INDEX) {
return ColumnTypeEnum.Text
}
return ColumnTypeEnum.Bytes
case 'BIT':
case 'BITNUM':
case 'HEXNUM':
Expand All @@ -95,7 +107,7 @@ export function fieldToColumnType(field: PlanetScaleColumnType): ColumnType {
// Fall back to Int32 for consistency with quaint.
return ColumnTypeEnum.Int32
default:
throw new Error(`Unsupported column type: ${field}`)
throw new Error(`Unsupported column type: ${type}`)
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/adapter-planetscale/src/planetscale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Debug, DriverAdapterError } from '@prisma/driver-adapter-utils'
import { Mutex } from 'async-mutex'

import { name as packageName } from '../package.json'
import { cast, fieldToColumnType, mapArg, type PlanetScaleColumnType } from './conversion'
import { cast, fieldToColumnType, mapArg } from './conversion'
import { createDeferred, Deferred } from './deferred'
import { convertDriverError } from './errors'

Expand Down Expand Up @@ -54,7 +54,7 @@ class PlanetScaleQueryable<ClientT extends planetScale.Client | planetScale.Tran
const columns = fields.map((field) => field.name)
return {
columnNames: columns,
columnTypes: fields.map((field) => fieldToColumnType(field.type as PlanetScaleColumnType)),
columnTypes: fields.map((field) => fieldToColumnType(field)),
rows: rows as SqlResultSet['rows'],
lastInsertId,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { defineMatrix } from '../../_utils/defineMatrix'
import { Providers } from '../../_utils/providers'

export default defineMatrix(() => [[{ provider: Providers.MYSQL }]])
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { idForProvider } from '../../../_utils/idForProvider'
import testMatrix from '../_matrix'

export default testMatrix.setupSchema(({ provider }) => {
return /* Prisma */ `
generator client {
provider = "prisma-client-js"
output = "../generated/prisma/client"
}

datasource db {
provider = "${provider}"
}

model User {
id ${idForProvider(provider)}
char_bin_collation String @db.Char(191)
varchar_bin_collation String @db.VarChar(191)
text_bin_collation String @db.Text
}
`
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Providers } from '../../_utils/providers'
import testMatrix from './_matrix'
// @ts-ignore
import type { PrismaClient } from './generated/prisma/client'

declare let prisma: PrismaClient

testMatrix.setupTestSuite(
() => {
beforeAll(async () => {
// Prisma Schema does not support specifying column collation
await prisma.$executeRaw`ALTER TABLE \`User\` MODIFY \`char_bin_collation\` CHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL`
await prisma.$executeRaw`ALTER TABLE \`User\` MODIFY \`varchar_bin_collation\` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL`
await prisma.$executeRaw`ALTER TABLE \`User\` MODIFY \`text_bin_collation\` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL`
})

beforeEach(async () => {
await prisma.user.deleteMany()
})

test('columns with _bin collation return strings, not Uint8Array', async () => {
await prisma.user.create({
data: {
char_bin_collation: 'hello',
varchar_bin_collation: 'hello',
text_bin_collation: 'hello',
},
})

const result =
(await prisma.$queryRaw`SELECT \`char_bin_collation\`, \`varchar_bin_collation\`, \`text_bin_collation\` FROM \`User\``) as Array<
Record<string, unknown>
>

expect(result).toHaveLength(1)
expect(result[0].char_bin_collation).toBe('hello')
expect(result[0].varchar_bin_collation).toBe('hello')
expect(result[0].text_bin_collation).toBe('hello')
})
},
{
optOut: {
from: [Providers.POSTGRESQL, Providers.SQLITE, Providers.MONGODB, Providers.COCKROACHDB, Providers.SQLSERVER],
reason: 'This test is for MySQL-specific column type detection',
},
},
)
Loading