From 42d7de9eee27adf5a4b60ca5461ac99b739af1e6 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Tue, 17 Dec 2024 10:50:41 -0800 Subject: [PATCH] Add SS support in drizzle-zod --- .../src/singlestore-core/columns/all.ts | 5 +- .../src/singlestore-core/columns/char.ts | 71 +-- .../src/singlestore-core/columns/text.ts | 2 +- .../src/singlestore-core/columns/varchar.ts | 71 +-- drizzle-zod/src/column.ts | 84 ++- drizzle-zod/tests/singlestore.test.ts | 491 ++++++++++++++++++ 6 files changed, 649 insertions(+), 75 deletions(-) create mode 100644 drizzle-zod/tests/singlestore.test.ts diff --git a/drizzle-orm/src/singlestore-core/columns/all.ts b/drizzle-orm/src/singlestore-core/columns/all.ts index 66d289e3f..450417060 100644 --- a/drizzle-orm/src/singlestore-core/columns/all.ts +++ b/drizzle-orm/src/singlestore-core/columns/all.ts @@ -15,7 +15,7 @@ import { mediumint } from './mediumint.ts'; import { real } from './real.ts'; import { serial } from './serial.ts'; import { smallint } from './smallint.ts'; -import { text } from './text.ts'; +import { longtext, mediumtext, text, tinytext } from './text.ts'; import { time } from './time.ts'; import { timestamp } from './timestamp.ts'; import { tinyint } from './tinyint.ts'; @@ -42,7 +42,10 @@ export function getSingleStoreColumnBuilders() { real, serial, smallint, + longtext, + mediumtext, text, + tinytext, time, timestamp, tinyint, diff --git a/drizzle-orm/src/singlestore-core/columns/char.ts b/drizzle-orm/src/singlestore-core/columns/char.ts index 512460f92..903946688 100644 --- a/drizzle-orm/src/singlestore-core/columns/char.ts +++ b/drizzle-orm/src/singlestore-core/columns/char.ts @@ -5,26 +5,31 @@ import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; import { getColumnNameAndConfig, type Writable } from '~/utils.ts'; import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; -export type SingleStoreCharBuilderInitial = - SingleStoreCharBuilder<{ - name: TName; - dataType: 'string'; - columnType: 'SingleStoreChar'; - data: TEnum[number]; - driverParam: number | string; - enumValues: TEnum; - generated: undefined; - }>; +export type SingleStoreCharBuilderInitial< + TName extends string, + TEnum extends [string, ...string[]], + TLength extends number | undefined, +> = SingleStoreCharBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreChar'; + data: TEnum[number]; + driverParam: number | string; + enumValues: TEnum; + generated: undefined; + length: TLength; +}>; -export class SingleStoreCharBuilder> - extends SingleStoreColumnBuilder< - T, - SingleStoreCharConfig - > -{ +export class SingleStoreCharBuilder< + T extends ColumnBuilderBaseConfig<'string', 'SingleStoreChar'> & { length?: number | undefined }, +> extends SingleStoreColumnBuilder< + T, + SingleStoreCharConfig, + { length: T['length'] } +> { static override readonly [entityKind]: string = 'SingleStoreCharBuilder'; - constructor(name: T['name'], config: SingleStoreCharConfig) { + constructor(name: T['name'], config: SingleStoreCharConfig) { super(name, 'string', 'SingleStoreChar'); this.config.length = config.length; this.config.enum = config.enum; @@ -33,20 +38,20 @@ export class SingleStoreCharBuilder( table: AnySingleStoreTable<{ name: TTableName }>, - ): SingleStoreChar & { enumValues: T['enumValues'] }> { - return new SingleStoreChar & { enumValues: T['enumValues'] }>( + ): SingleStoreChar & { length: T['length']; enumValues: T['enumValues'] }> { + return new SingleStoreChar & { length: T['length']; enumValues: T['enumValues'] }>( table, this.config as ColumnBuilderRuntimeConfig, ); } } -export class SingleStoreChar> - extends SingleStoreColumn> +export class SingleStoreChar & { length?: number | undefined }> + extends SingleStoreColumn, { length: T['length'] }> { static override readonly [entityKind]: string = 'SingleStoreChar'; - readonly length: number | undefined = this.config.length; + readonly length: T['length'] = this.config.length; override readonly enumValues = this.config.enum; getSQLType(): string { @@ -56,19 +61,25 @@ export class SingleStoreChar { - length?: number; enum?: TEnum; + length?: TLength; } -export function char(): SingleStoreCharBuilderInitial<'', [string, ...string[]]>; -export function char>( - config?: SingleStoreCharConfig>, -): SingleStoreCharBuilderInitial<'', Writable>; -export function char>( +export function char(): SingleStoreCharBuilderInitial<'', [string, ...string[]], undefined>; +export function char, L extends number | undefined>( + config?: SingleStoreCharConfig, L>, +): SingleStoreCharBuilderInitial<'', Writable, L>; +export function char< + TName extends string, + U extends string, + T extends Readonly<[U, ...U[]]>, + L extends number | undefined, +>( name: TName, - config?: SingleStoreCharConfig>, -): SingleStoreCharBuilderInitial>; + config?: SingleStoreCharConfig, L>, +): SingleStoreCharBuilderInitial, L>; export function char(a?: string | SingleStoreCharConfig, b: SingleStoreCharConfig = {}): any { const { name, config } = getColumnNameAndConfig(a, b); return new SingleStoreCharBuilder(name, config as any); diff --git a/drizzle-orm/src/singlestore-core/columns/text.ts b/drizzle-orm/src/singlestore-core/columns/text.ts index 425da550f..d4d46ab2c 100644 --- a/drizzle-orm/src/singlestore-core/columns/text.ts +++ b/drizzle-orm/src/singlestore-core/columns/text.ts @@ -48,7 +48,7 @@ export class SingleStoreText = - SingleStoreVarCharBuilder< - { - name: TName; - dataType: 'string'; - columnType: 'SingleStoreVarChar'; - data: TEnum[number]; - driverParam: number | string; - enumValues: TEnum; - generated: undefined; - } - >; +export type SingleStoreVarCharBuilderInitial< + TName extends string, + TEnum extends [string, ...string[]], + TLength extends number | undefined, +> = SingleStoreVarCharBuilder< + { + name: TName; + dataType: 'string'; + columnType: 'SingleStoreVarChar'; + data: TEnum[number]; + driverParam: number | string; + enumValues: TEnum; + generated: undefined; + length: TLength; + } +>; -export class SingleStoreVarCharBuilder> - extends SingleStoreColumnBuilder> -{ +export class SingleStoreVarCharBuilder< + T extends ColumnBuilderBaseConfig<'string', 'SingleStoreVarChar'> & { length?: number | undefined }, +> extends SingleStoreColumnBuilder, { length: T['length'] }> { static override readonly [entityKind]: string = 'SingleStoreVarCharBuilder'; /** @internal */ - constructor(name: T['name'], config: SingleStoreVarCharConfig) { + constructor(name: T['name'], config: SingleStoreVarCharConfig) { super(name, 'string', 'SingleStoreVarChar'); this.config.length = config.length; this.config.enum = config.enum; @@ -33,21 +37,22 @@ export class SingleStoreVarCharBuilder( table: AnySingleStoreTable<{ name: TTableName }>, - ): SingleStoreVarChar & { enumValues: T['enumValues'] }> { - return new SingleStoreVarChar & { enumValues: T['enumValues'] }>( + ): SingleStoreVarChar & { length: T['length']; enumValues: T['enumValues'] }> { + return new SingleStoreVarChar< + MakeColumnConfig & { length: T['length']; enumValues: T['enumValues'] } + >( table, this.config as ColumnBuilderRuntimeConfig, ); } } -export class SingleStoreVarChar> - extends SingleStoreColumn> -{ +export class SingleStoreVarChar< + T extends ColumnBaseConfig<'string', 'SingleStoreVarChar'> & { length?: number | undefined }, +> extends SingleStoreColumn, { length: T['length'] }> { static override readonly [entityKind]: string = 'SingleStoreVarChar'; - readonly length: number | undefined = this.config.length; - + readonly length: T['length'] = this.config.length; override readonly enumValues = this.config.enum; getSQLType(): string { @@ -57,18 +62,24 @@ export class SingleStoreVarChar { - length: number; enum?: TEnum; + length?: TLength; } -export function varchar>( - config: SingleStoreVarCharConfig>, -): SingleStoreVarCharBuilderInitial<'', Writable>; -export function varchar>( +export function varchar, L extends number | undefined>( + config: SingleStoreVarCharConfig, L>, +): SingleStoreVarCharBuilderInitial<'', Writable, L>; +export function varchar< + TName extends string, + U extends string, + T extends Readonly<[U, ...U[]]>, + L extends number | undefined, +>( name: TName, - config: SingleStoreVarCharConfig>, -): SingleStoreVarCharBuilderInitial>; + config: SingleStoreVarCharConfig, L>, +): SingleStoreVarCharBuilderInitial, L>; export function varchar(a?: string | SingleStoreVarCharConfig, b?: SingleStoreVarCharConfig): any { const { name, config } = getColumnNameAndConfig(a, b); return new SingleStoreVarCharBuilder(name, config as any); diff --git a/drizzle-zod/src/column.ts b/drizzle-zod/src/column.ts index d6fff3445..23bc3c142 100644 --- a/drizzle-zod/src/column.ts +++ b/drizzle-zod/src/column.ts @@ -37,6 +37,21 @@ import type { PgVarchar, PgVector, } from 'drizzle-orm/pg-core'; +import type { + SingleStoreBigInt53, + SingleStoreChar, + SingleStoreDouble, + SingleStoreFloat, + SingleStoreInt, + SingleStoreMediumInt, + SingleStoreReal, + SingleStoreSerial, + SingleStoreSmallInt, + SingleStoreText, + SingleStoreTinyInt, + SingleStoreVarChar, + SingleStoreYear, +} from 'drizzle-orm/singlestore-core'; import type { SQLiteInteger, SQLiteReal, SQLiteText } from 'drizzle-orm/sqlite-core'; import { z } from 'zod'; import type { z as zod } from 'zod'; @@ -114,57 +129,92 @@ function numberColumnToSchema(column: Column, z: typeof zod): z.ZodTypeAny { let max!: number; let integer = false; - if (isColumnType>(column, ['MySqlTinyInt'])) { + if (isColumnType | SingleStoreTinyInt>(column, ['MySqlTinyInt', 'SingleStoreTinyInt'])) { min = unsigned ? 0 : CONSTANTS.INT8_MIN; max = unsigned ? CONSTANTS.INT8_UNSIGNED_MAX : CONSTANTS.INT8_MAX; integer = true; } else if ( - isColumnType | PgSmallSerial | MySqlSmallInt>(column, [ + isColumnType | PgSmallSerial | MySqlSmallInt | SingleStoreSmallInt>(column, [ 'PgSmallInt', 'PgSmallSerial', 'MySqlSmallInt', + 'SingleStoreSmallInt', ]) ) { min = unsigned ? 0 : CONSTANTS.INT16_MIN; max = unsigned ? CONSTANTS.INT16_UNSIGNED_MAX : CONSTANTS.INT16_MAX; integer = true; } else if ( - isColumnType | MySqlFloat | MySqlMediumInt>(column, [ + isColumnType< + PgReal | MySqlFloat | MySqlMediumInt | SingleStoreMediumInt | SingleStoreFloat + >(column, [ 'PgReal', 'MySqlFloat', 'MySqlMediumInt', + 'SingleStoreMediumInt', + 'SingleStoreFloat', ]) ) { min = unsigned ? 0 : CONSTANTS.INT24_MIN; max = unsigned ? CONSTANTS.INT24_UNSIGNED_MAX : CONSTANTS.INT24_MAX; - integer = isColumnType(column, ['MySqlMediumInt']); + integer = isColumnType(column, ['MySqlMediumInt', 'SingleStoreMediumInt']); } else if ( - isColumnType | PgSerial | MySqlInt>(column, ['PgInteger', 'PgSerial', 'MySqlInt']) + isColumnType | PgSerial | MySqlInt | SingleStoreInt>(column, [ + 'PgInteger', + 'PgSerial', + 'MySqlInt', + 'SingleStoreInt', + ]) ) { min = unsigned ? 0 : CONSTANTS.INT32_MIN; max = unsigned ? CONSTANTS.INT32_UNSIGNED_MAX : CONSTANTS.INT32_MAX; integer = true; } else if ( - isColumnType | MySqlReal | MySqlDouble | SQLiteReal>(column, [ + isColumnType< + | PgDoublePrecision + | MySqlReal + | MySqlDouble + | SingleStoreReal + | SingleStoreDouble + | SQLiteReal + >(column, [ 'PgDoublePrecision', 'MySqlReal', 'MySqlDouble', + 'SingleStoreReal', + 'SingleStoreDouble', 'SQLiteReal', ]) ) { min = unsigned ? 0 : CONSTANTS.INT48_MIN; max = unsigned ? CONSTANTS.INT48_UNSIGNED_MAX : CONSTANTS.INT48_MAX; } else if ( - isColumnType | PgBigSerial53 | MySqlBigInt53 | MySqlSerial | SQLiteInteger>( + isColumnType< + | PgBigInt53 + | PgBigSerial53 + | MySqlBigInt53 + | MySqlSerial + | SingleStoreBigInt53 + | SingleStoreSerial + | SQLiteInteger + >( column, - ['PgBigInt53', 'PgBigSerial53', 'MySqlBigInt53', 'MySqlSerial', 'SQLiteInteger'], + [ + 'PgBigInt53', + 'PgBigSerial53', + 'MySqlBigInt53', + 'MySqlSerial', + 'SingleStoreBigInt53', + 'SingleStoreSerial', + 'SQLiteInteger', + ], ) ) { - unsigned = unsigned || isColumnType(column, ['MySqlSerial']); + unsigned = unsigned || isColumnType(column, ['MySqlSerial', 'SingleStoreSerial']); min = unsigned ? 0 : Number.MIN_SAFE_INTEGER; max = Number.MAX_SAFE_INTEGER; integer = true; - } else if (isColumnType>(column, ['MySqlYear'])) { + } else if (isColumnType | SingleStoreYear>(column, ['MySqlYear', 'SingleStoreYear'])) { min = 1901; max = 2155; integer = true; @@ -196,9 +246,11 @@ function stringColumnToSchema(column: Column, z: typeof zod): z.ZodTypeAny { if (isColumnType | SQLiteText>(column, ['PgVarchar', 'SQLiteText'])) { max = column.length; - } else if (isColumnType>(column, ['MySqlVarChar'])) { + } else if ( + isColumnType | SingleStoreVarChar>(column, ['MySqlVarChar', 'SingleStoreVarChar']) + ) { max = column.length ?? CONSTANTS.INT16_UNSIGNED_MAX; - } else if (isColumnType>(column, ['MySqlText'])) { + } else if (isColumnType | SingleStoreText>(column, ['MySqlText', 'SingleStoreText'])) { if (column.textType === 'longtext') { max = CONSTANTS.INT32_UNSIGNED_MAX; } else if (column.textType === 'mediumtext') { @@ -210,7 +262,13 @@ function stringColumnToSchema(column: Column, z: typeof zod): z.ZodTypeAny { } } - if (isColumnType | MySqlChar>(column, ['PgChar', 'MySqlChar'])) { + if ( + isColumnType | MySqlChar | SingleStoreChar>(column, [ + 'PgChar', + 'MySqlChar', + 'SingleStoreChar', + ]) + ) { max = column.length; fixed = true; } diff --git a/drizzle-zod/tests/singlestore.test.ts b/drizzle-zod/tests/singlestore.test.ts new file mode 100644 index 000000000..b91c74be8 --- /dev/null +++ b/drizzle-zod/tests/singlestore.test.ts @@ -0,0 +1,491 @@ +import { type Equal } from 'drizzle-orm'; +import { customType, int, serial, singlestoreSchema, singlestoreTable, text } from 'drizzle-orm/singlestore-core'; +import { test } from 'vitest'; +import { z } from 'zod'; +import { jsonSchema } from '~/column.ts'; +import { CONSTANTS } from '~/constants.ts'; +import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; +import { Expect, expectSchemaShape } from './utils.ts'; + +const intSchema = z.number().min(CONSTANTS.INT32_MIN).max(CONSTANTS.INT32_MAX).int(); +const serialNumberModeSchema = z.number().min(0).max(Number.MAX_SAFE_INTEGER).int(); +const textSchema = z.string().max(CONSTANTS.INT16_UNSIGNED_MAX); + +test('table - select', (t) => { + const table = singlestoreTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = z.object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('table in schema - select', (tc) => { + const schema = singlestoreSchema('test'); + const table = schema.table('test', { + id: serial().primaryKey(), + name: text().notNull(), + }); + + const result = createSelectSchema(table); + const expected = z.object({ id: serialNumberModeSchema, name: textSchema }); + expectSchemaShape(tc, expected).from(result); + Expect>(); +}); + +test('table - insert', (t) => { + const table = singlestoreTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); + + const result = createInsertSchema(table); + const expected = z.object({ + id: serialNumberModeSchema.optional(), + name: textSchema, + age: intSchema.nullable().optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('table - update', (t) => { + const table = singlestoreTable('test', { + id: serial().primaryKey(), + name: text().notNull(), + age: int(), + }); + + const result = createUpdateSchema(table); + const expected = z.object({ + id: serialNumberModeSchema.optional(), + name: textSchema.optional(), + age: intSchema.nullable().optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +// TODO: SingleStore doesn't support views yet. Add these tests when they're added + +// test('view qb - select', (t) => { +// const table = singlestoreTable('test', { +// id: serial().primaryKey(), +// name: text().notNull(), +// }); +// const view = mysqlView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); + +// const result = createSelectSchema(view); +// const expected = z.object({ id: serialNumberModeSchema, age: z.any() }); +// expectSchemaShape(t, expected).from(result); +// Expect>(); +// }); + +// test('view columns - select', (t) => { +// const view = mysqlView('test', { +// id: serial().primaryKey(), +// name: text().notNull(), +// }).as(sql``); + +// const result = createSelectSchema(view); +// const expected = z.object({ id: serialNumberModeSchema, name: textSchema }); +// expectSchemaShape(t, expected).from(result); +// Expect>(); +// }); + +// test('view with nested fields - select', (t) => { +// const table = singlestoreTable('test', { +// id: serial().primaryKey(), +// name: text().notNull(), +// }); +// const view = mysqlView('test').as((qb) => +// qb.select({ +// id: table.id, +// nested: { +// name: table.name, +// age: sql``.as('age'), +// }, +// table, +// }).from(table) +// ); + +// const result = createSelectSchema(view); +// const expected = z.object({ +// id: serialNumberModeSchema, +// nested: z.object({ name: textSchema, age: z.any() }), +// table: z.object({ id: serialNumberModeSchema, name: textSchema }), +// }); +// expectSchemaShape(t, expected).from(result); +// Expect>(); +// }); + +test('nullability - select', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + }); + + const result = createSelectSchema(table); + const expected = z.object({ + c1: intSchema.nullable(), + c2: intSchema, + c3: intSchema.nullable(), + c4: intSchema, + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('nullability - insert', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table); + const expected = z.object({ + c1: intSchema.nullable().optional(), + c2: intSchema, + c3: intSchema.nullable().optional(), + c4: intSchema.optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('nullability - update', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().default(1), + c4: int().notNull().default(1), + c5: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table); + const expected = z.object({ + c1: intSchema.nullable().optional(), + c2: intSchema.optional(), + c3: intSchema.nullable().optional(), + c4: intSchema.optional(), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - select', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + }); + + const result = createSelectSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); + const expected = z.object({ + c1: intSchema.nullable(), + c2: intSchema.max(1000), + c3: z.string().transform(Number), + }); + + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - select with custom data type', (t) => { + const customText = customType({ dataType: () => 'text' }); + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: customText(), + }); + + const customTextSchema = z.string().min(1).max(100); + const result = createSelectSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + c4: customTextSchema, + }); + const expected = z.object({ + c1: intSchema.nullable(), + c2: intSchema.max(1000), + c3: z.string().transform(Number), + c4: customTextSchema, + }); + + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - insert', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createInsertSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); + const expected = z.object({ + c1: intSchema.nullable().optional(), + c2: intSchema.max(1000), + c3: z.string().transform(Number), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +test('refine table - update', (t) => { + const table = singlestoreTable('test', { + c1: int(), + c2: int().notNull(), + c3: int().notNull(), + c4: int().generatedAlwaysAs(1), + }); + + const result = createUpdateSchema(table, { + c2: (schema) => schema.max(1000), + c3: z.string().transform(Number), + }); + const expected = z.object({ + c1: intSchema.nullable().optional(), + c2: intSchema.max(1000).optional(), + c3: z.string().transform(Number), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +// test('refine view - select', (t) => { +// const table = singlestoreTable('test', { +// c1: int(), +// c2: int(), +// c3: int(), +// c4: int(), +// c5: int(), +// c6: int(), +// }); +// const view = mysqlView('test').as((qb) => +// qb.select({ +// c1: table.c1, +// c2: table.c2, +// c3: table.c3, +// nested: { +// c4: table.c4, +// c5: table.c5, +// c6: table.c6, +// }, +// table, +// }).from(table) +// ); + +// const result = createSelectSchema(view, { +// c2: (schema) => schema.max(1000), +// c3: z.string().transform(Number), +// nested: { +// c5: (schema) => schema.max(1000), +// c6: z.string().transform(Number), +// }, +// table: { +// c2: (schema) => schema.max(1000), +// c3: z.string().transform(Number), +// }, +// }); +// const expected = z.object({ +// c1: intSchema.nullable(), +// c2: intSchema.max(1000).nullable(), +// c3: z.string().transform(Number), +// nested: z.object({ +// c4: intSchema.nullable(), +// c5: intSchema.max(1000).nullable(), +// c6: z.string().transform(Number), +// }), +// table: z.object({ +// c1: intSchema.nullable(), +// c2: intSchema.max(1000).nullable(), +// c3: z.string().transform(Number), +// c4: intSchema.nullable(), +// c5: intSchema.nullable(), +// c6: intSchema.nullable(), +// }), +// }); +// expectSchemaShape(t, expected).from(result); +// Expect>(); +// }); + +test('all data types', (t) => { + const table = singlestoreTable('test', ({ + bigint, + binary, + boolean, + char, + date, + datetime, + decimal, + double, + float, + int, + json, + mediumint, + singlestoreEnum, + real, + serial, + smallint, + text, + time, + timestamp, + tinyint, + varchar, + varbinary, + year, + longtext, + mediumtext, + tinytext, + }) => ({ + bigint1: bigint({ mode: 'number' }).notNull(), + bigint2: bigint({ mode: 'bigint' }).notNull(), + bigint3: bigint({ unsigned: true, mode: 'number' }).notNull(), + bigint4: bigint({ unsigned: true, mode: 'bigint' }).notNull(), + binary: binary({ length: 10 }).notNull(), + boolean: boolean().notNull(), + char1: char({ length: 10 }).notNull(), + char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + date1: date({ mode: 'date' }).notNull(), + date2: date({ mode: 'string' }).notNull(), + datetime1: datetime({ mode: 'date' }).notNull(), + datetime2: datetime({ mode: 'string' }).notNull(), + decimal1: decimal().notNull(), + decimal2: decimal({ unsigned: true }).notNull(), + double1: double().notNull(), + double2: double({ unsigned: true }).notNull(), + float1: float().notNull(), + float2: float({ unsigned: true }).notNull(), + int1: int().notNull(), + int2: int({ unsigned: true }).notNull(), + json: json().notNull(), + mediumint1: mediumint().notNull(), + mediumint2: mediumint({ unsigned: true }).notNull(), + enum: singlestoreEnum('enum', ['a', 'b', 'c']).notNull(), + real: real().notNull(), + serial: serial().notNull(), + smallint1: smallint().notNull(), + smallint2: smallint({ unsigned: true }).notNull(), + text1: text().notNull(), + text2: text({ enum: ['a', 'b', 'c'] }).notNull(), + time: time().notNull(), + timestamp1: timestamp({ mode: 'date' }).notNull(), + timestamp2: timestamp({ mode: 'string' }).notNull(), + tinyint1: tinyint().notNull(), + tinyint2: tinyint({ unsigned: true }).notNull(), + varchar1: varchar({ length: 10 }).notNull(), + varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), + varbinary: varbinary({ length: 10 }).notNull(), + year: year().notNull(), + longtext1: longtext().notNull(), + longtext2: longtext({ enum: ['a', 'b', 'c'] }).notNull(), + mediumtext1: mediumtext().notNull(), + mediumtext2: mediumtext({ enum: ['a', 'b', 'c'] }).notNull(), + tinytext1: tinytext().notNull(), + tinytext2: tinytext({ enum: ['a', 'b', 'c'] }).notNull(), + })); + + const result = createSelectSchema(table); + const expected = z.object({ + bigint1: z.number().min(Number.MIN_SAFE_INTEGER).max(Number.MAX_SAFE_INTEGER).int(), + bigint2: z.bigint().min(CONSTANTS.INT64_MIN).max(CONSTANTS.INT64_MAX), + bigint3: z.number().min(0).max(Number.MAX_SAFE_INTEGER).int(), + bigint4: z.bigint().min(0n).max(CONSTANTS.INT64_UNSIGNED_MAX), + binary: z.string(), + boolean: z.boolean(), + char1: z.string().length(10), + char2: z.enum(['a', 'b', 'c']), + date1: z.date(), + date2: z.string(), + datetime1: z.date(), + datetime2: z.string(), + decimal1: z.string(), + decimal2: z.string(), + double1: z.number().min(CONSTANTS.INT48_MIN).max(CONSTANTS.INT48_MAX), + double2: z.number().min(0).max(CONSTANTS.INT48_UNSIGNED_MAX), + float1: z.number().min(CONSTANTS.INT24_MIN).max(CONSTANTS.INT24_MAX), + float2: z.number().min(0).max(CONSTANTS.INT24_UNSIGNED_MAX), + int1: z.number().min(CONSTANTS.INT32_MIN).max(CONSTANTS.INT32_MAX).int(), + int2: z.number().min(0).max(CONSTANTS.INT32_UNSIGNED_MAX).int(), + json: jsonSchema, + mediumint1: z.number().min(CONSTANTS.INT24_MIN).max(CONSTANTS.INT24_MAX).int(), + mediumint2: z.number().min(0).max(CONSTANTS.INT24_UNSIGNED_MAX).int(), + enum: z.enum(['a', 'b', 'c']), + real: z.number().min(CONSTANTS.INT48_MIN).max(CONSTANTS.INT48_MAX), + serial: z.number().min(0).max(Number.MAX_SAFE_INTEGER).int(), + smallint1: z.number().min(CONSTANTS.INT16_MIN).max(CONSTANTS.INT16_MAX).int(), + smallint2: z.number().min(0).max(CONSTANTS.INT16_UNSIGNED_MAX).int(), + text1: z.string().max(CONSTANTS.INT16_UNSIGNED_MAX), + text2: z.enum(['a', 'b', 'c']), + time: z.string(), + timestamp1: z.date(), + timestamp2: z.string(), + tinyint1: z.number().min(CONSTANTS.INT8_MIN).max(CONSTANTS.INT8_MAX).int(), + tinyint2: z.number().min(0).max(CONSTANTS.INT8_UNSIGNED_MAX).int(), + varchar1: z.string().max(10), + varchar2: z.enum(['a', 'b', 'c']), + varbinary: z.string(), + year: z.number().min(1901).max(2155).int(), + longtext1: z.string().max(CONSTANTS.INT32_UNSIGNED_MAX), + longtext2: z.enum(['a', 'b', 'c']), + mediumtext1: z.string().max(CONSTANTS.INT24_UNSIGNED_MAX), + mediumtext2: z.enum(['a', 'b', 'c']), + tinytext1: z.string().max(CONSTANTS.INT8_UNSIGNED_MAX), + tinytext2: z.enum(['a', 'b', 'c']), + }); + expectSchemaShape(t, expected).from(result); + Expect>(); +}); + +/* Disallow unknown keys in table refinement - select */ { + const table = singlestoreTable('test', { id: int() }); + // @ts-expect-error + createSelectSchema(table, { unknown: z.string() }); +} + +/* Disallow unknown keys in table refinement - insert */ { + const table = singlestoreTable('test', { id: int() }); + // @ts-expect-error + createInsertSchema(table, { unknown: z.string() }); +} + +/* Disallow unknown keys in table refinement - update */ { + const table = singlestoreTable('test', { id: int() }); + // @ts-expect-error + createUpdateSchema(table, { unknown: z.string() }); +} + +// /* Disallow unknown keys in view qb - select */ { +// const table = singlestoreTable('test', { id: int() }); +// const view = mysqlView('test').as((qb) => qb.select().from(table)); +// const nestedSelect = mysqlView('test').as((qb) => qb.select({ table }).from(table)); +// // @ts-expect-error +// createSelectSchema(view, { unknown: z.string() }); +// // @ts-expect-error +// createSelectSchema(nestedSelect, { table: { unknown: z.string() } }); +// } + +// /* Disallow unknown keys in view columns - select */ { +// const view = mysqlView('test', { id: int() }).as(sql``); +// // @ts-expect-error +// createSelectSchema(view, { unknown: z.string() }); +// }