import { type Equal, sql } from 'drizzle-orm'; import { customType, integer, json, jsonb, pgEnum, pgMaterializedView, pgSchema, pgTable, pgView, serial, text, } from 'drizzle-orm/pg-core'; import type { TopLevelCondition } from 'json-rules-engine'; import * as v from 'valibot'; import { test } from 'vitest'; import { jsonSchema } from '~/column.ts'; import { CONSTANTS } from '~/constants.ts'; import { createInsertSchema, createSelectSchema, createUpdateSchema } from '../src'; import { Expect, expectEnumValues, expectSchemaShape } from './utils.ts'; const integerSchema = v.pipe(v.number(), v.minValue(CONSTANTS.INT32_MIN), v.maxValue(CONSTANTS.INT32_MAX), v.integer()); const textSchema = v.string(); test('table - select', (t) => { const table = pgTable('test', { id: serial().primaryKey(), name: text().notNull(), }); const result = createSelectSchema(table); const expected = v.object({ id: integerSchema, name: textSchema }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('table in schema - select', (tc) => { const schema = pgSchema('test'); const table = schema.table('test', { id: serial().primaryKey(), name: text().notNull(), }); const result = createSelectSchema(table); const expected = v.object({ id: integerSchema, name: textSchema }); expectSchemaShape(tc, expected).from(result); Expect>(); }); test('table - insert', (t) => { const table = pgTable('test', { id: integer().generatedAlwaysAsIdentity().primaryKey(), name: text().notNull(), age: integer(), }); const result = createInsertSchema(table); const expected = v.object({ name: textSchema, age: v.optional(v.nullable(integerSchema)) }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('table - update', (t) => { const table = pgTable('test', { id: integer().generatedAlwaysAsIdentity().primaryKey(), name: text().notNull(), age: integer(), }); const result = createUpdateSchema(table); const expected = v.object({ name: v.optional(textSchema), age: v.optional(v.nullable(integerSchema)), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('view qb - select', (t) => { const table = pgTable('test', { id: serial().primaryKey(), name: text().notNull(), }); const view = pgView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); const result = createSelectSchema(view); const expected = v.object({ id: integerSchema, age: v.any() }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('view columns - select', (t) => { const view = pgView('test', { id: serial().primaryKey(), name: text().notNull(), }).as(sql``); const result = createSelectSchema(view); const expected = v.object({ id: integerSchema, name: textSchema }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('materialized view qb - select', (t) => { const table = pgTable('test', { id: serial().primaryKey(), name: text().notNull(), }); const view = pgMaterializedView('test').as((qb) => qb.select({ id: table.id, age: sql``.as('age') }).from(table)); const result = createSelectSchema(view); const expected = v.object({ id: integerSchema, age: v.any() }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('materialized view columns - select', (t) => { const view = pgView('test', { id: serial().primaryKey(), name: text().notNull(), }).as(sql``); const result = createSelectSchema(view); const expected = v.object({ id: integerSchema, name: textSchema }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('view with nested fields - select', (t) => { const table = pgTable('test', { id: serial().primaryKey(), name: text().notNull(), }); const view = pgMaterializedView('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 = v.object({ id: integerSchema, nested: v.object({ name: textSchema, age: v.any() }), table: v.object({ id: integerSchema, name: textSchema }), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('enum - select', (t) => { const enum_ = pgEnum('test', ['a', 'b', 'c']); const result = createSelectSchema(enum_); const expected = v.enum({ a: 'a', b: 'b', c: 'c' }); expectEnumValues(t, expected).from(result); Expect>(); }); test('nullability - select', (t) => { const table = pgTable('test', { c1: integer(), c2: integer().notNull(), c3: integer().default(1), c4: integer().notNull().default(1), }); const result = createSelectSchema(table); const expected = v.object({ c1: v.nullable(integerSchema), c2: integerSchema, c3: v.nullable(integerSchema), c4: integerSchema, }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('nullability - insert', (t) => { const table = pgTable('test', { c1: integer(), c2: integer().notNull(), c3: integer().default(1), c4: integer().notNull().default(1), c5: integer().generatedAlwaysAs(1), c6: integer().generatedAlwaysAsIdentity(), c7: integer().generatedByDefaultAsIdentity(), }); const result = createInsertSchema(table); const expected = v.object({ c1: v.optional(v.nullable(integerSchema)), c2: integerSchema, c3: v.optional(v.nullable(integerSchema)), c4: v.optional(integerSchema), c7: v.optional(integerSchema), }); expectSchemaShape(t, expected).from(result); }); test('nullability - update', (t) => { const table = pgTable('test', { c1: integer(), c2: integer().notNull(), c3: integer().default(1), c4: integer().notNull().default(1), c5: integer().generatedAlwaysAs(1), c6: integer().generatedAlwaysAsIdentity(), c7: integer().generatedByDefaultAsIdentity(), }); const result = createUpdateSchema(table); const expected = v.object({ c1: v.optional(v.nullable(integerSchema)), c2: v.optional(integerSchema), c3: v.optional(v.nullable(integerSchema)), c4: v.optional(integerSchema), c7: v.optional(integerSchema), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('refine table - select', (t) => { const table = pgTable('test', { c1: integer(), c2: integer().notNull(), c3: integer().notNull(), }); const result = createSelectSchema(table, { c2: (schema) => v.pipe(schema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), }); const expected = v.object({ c1: v.nullable(integerSchema), c2: v.pipe(integerSchema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('refine table - select with custom data type', (t) => { const customText = customType({ dataType: () => 'text' }); const table = pgTable('test', { c1: integer(), c2: integer().notNull(), c3: integer().notNull(), c4: customText(), }); const customTextSchema = v.pipe(v.string(), v.minLength(1), v.maxLength(100)); const result = createSelectSchema(table, { c2: (schema) => v.pipe(schema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), c4: customTextSchema, }); const expected = v.object({ c1: v.nullable(integerSchema), c2: v.pipe(integerSchema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), c4: customTextSchema, }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('refine table - insert', (t) => { const table = pgTable('test', { c1: integer(), c2: integer().notNull(), c3: integer().notNull(), c4: integer().generatedAlwaysAs(1), }); const result = createInsertSchema(table, { c2: (schema) => v.pipe(schema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), }); const expected = v.object({ c1: v.optional(v.nullable(integerSchema)), c2: v.pipe(integerSchema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('refine table - update', (t) => { const table = pgTable('test', { c1: integer(), c2: integer().notNull(), c3: integer().notNull(), c4: integer().generatedAlwaysAs(1), }); const result = createUpdateSchema(table, { c2: (schema) => v.pipe(schema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), }); const expected = v.object({ c1: v.optional(v.nullable(integerSchema)), c2: v.optional(v.pipe(integerSchema, v.maxValue(1000))), c3: v.pipe(v.string(), v.transform(Number)), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('refine view - select', (t) => { const table = pgTable('test', { c1: integer(), c2: integer(), c3: integer(), c4: integer(), c5: integer(), c6: integer(), }); const view = pgView('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) => v.pipe(schema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), nested: { c5: (schema) => v.pipe(schema, v.maxValue(1000)), c6: v.pipe(v.string(), v.transform(Number)), }, table: { c2: (schema) => v.pipe(schema, v.maxValue(1000)), c3: v.pipe(v.string(), v.transform(Number)), }, }); const expected = v.object({ c1: v.nullable(integerSchema), c2: v.nullable(v.pipe(integerSchema, v.maxValue(1000))), c3: v.pipe(v.string(), v.transform(Number)), nested: v.object({ c4: v.nullable(integerSchema), c5: v.nullable(v.pipe(integerSchema, v.maxValue(1000))), c6: v.pipe(v.string(), v.transform(Number)), }), table: v.object({ c1: v.nullable(integerSchema), c2: v.nullable(v.pipe(integerSchema, v.maxValue(1000))), c3: v.pipe(v.string(), v.transform(Number)), c4: v.nullable(integerSchema), c5: v.nullable(integerSchema), c6: v.nullable(integerSchema), }), }); expectSchemaShape(t, expected).from(result); Expect>(); }); test('all data types', (t) => { const table = pgTable('test', ({ bigint, bigserial, bit, boolean, date, char, cidr, doublePrecision, geometry, halfvec, inet, integer, interval, json, jsonb, line, macaddr, macaddr8, numeric, point, real, serial, smallint, smallserial, text, sparsevec, time, timestamp, uuid, varchar, vector, }) => ({ bigint1: bigint({ mode: 'number' }).notNull(), bigint2: bigint({ mode: 'bigint' }).notNull(), bigserial1: bigserial({ mode: 'number' }).notNull(), bigserial2: bigserial({ mode: 'bigint' }).notNull(), bit: bit({ dimensions: 5 }).notNull(), boolean: boolean().notNull(), date1: date({ mode: 'date' }).notNull(), date2: date({ mode: 'string' }).notNull(), char1: char({ length: 10 }).notNull(), char2: char({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), cidr: cidr().notNull(), doublePrecision: doublePrecision().notNull(), geometry1: geometry({ type: 'point', mode: 'tuple' }).notNull(), geometry2: geometry({ type: 'point', mode: 'xy' }).notNull(), halfvec: halfvec({ dimensions: 3 }).notNull(), inet: inet().notNull(), integer: integer().notNull(), interval: interval().notNull(), json: json().notNull(), jsonb: jsonb().notNull(), line1: line({ mode: 'abc' }).notNull(), line2: line({ mode: 'tuple' }).notNull(), macaddr: macaddr().notNull(), macaddr8: macaddr8().notNull(), numeric: numeric().notNull(), point1: point({ mode: 'xy' }).notNull(), point2: point({ mode: 'tuple' }).notNull(), real: real().notNull(), serial: serial().notNull(), smallint: smallint().notNull(), smallserial: smallserial().notNull(), text1: text().notNull(), text2: text({ enum: ['a', 'b', 'c'] }).notNull(), sparsevec: sparsevec({ dimensions: 3 }).notNull(), time: time().notNull(), timestamp1: timestamp({ mode: 'date' }).notNull(), timestamp2: timestamp({ mode: 'string' }).notNull(), uuid: uuid().notNull(), varchar1: varchar({ length: 10 }).notNull(), varchar2: varchar({ length: 1, enum: ['a', 'b', 'c'] }).notNull(), vector: vector({ dimensions: 3 }).notNull(), array1: integer().array().notNull(), array2: integer().array().array(2).notNull(), array3: varchar({ length: 10 }).array().array(2).notNull(), })); const result = createSelectSchema(table); const expected = v.object({ bigint1: v.pipe(v.number(), v.minValue(Number.MIN_SAFE_INTEGER), v.maxValue(Number.MAX_SAFE_INTEGER), v.integer()), bigint2: v.pipe(v.bigint(), v.minValue(CONSTANTS.INT64_MIN), v.maxValue(CONSTANTS.INT64_MAX)), bigserial1: v.pipe( v.number(), v.minValue(Number.MIN_SAFE_INTEGER), v.maxValue(Number.MAX_SAFE_INTEGER), v.integer(), ), bigserial2: v.pipe(v.bigint(), v.minValue(CONSTANTS.INT64_MIN), v.maxValue(CONSTANTS.INT64_MAX)), bit: v.pipe(v.string(), v.regex(/^[01]+$/), v.maxLength(5 as number)), boolean: v.boolean(), date1: v.date(), date2: v.string(), char1: v.pipe(v.string(), v.length(10 as number)), char2: v.enum({ a: 'a', b: 'b', c: 'c' }), cidr: v.string(), doublePrecision: v.pipe(v.number(), v.minValue(CONSTANTS.INT48_MIN), v.maxValue(CONSTANTS.INT48_MAX)), geometry1: v.tuple([v.number(), v.number()]), geometry2: v.object({ x: v.number(), y: v.number() }), halfvec: v.pipe(v.array(v.number()), v.length(3 as number)), inet: v.string(), integer: v.pipe(v.number(), v.minValue(CONSTANTS.INT32_MIN), v.maxValue(CONSTANTS.INT32_MAX), v.integer()), interval: v.string(), json: jsonSchema, jsonb: jsonSchema, line1: v.object({ a: v.number(), b: v.number(), c: v.number() }), line2: v.tuple([v.number(), v.number(), v.number()]), macaddr: v.string(), macaddr8: v.string(), numeric: v.string(), point1: v.object({ x: v.number(), y: v.number() }), point2: v.tuple([v.number(), v.number()]), real: v.pipe(v.number(), v.minValue(CONSTANTS.INT24_MIN), v.maxValue(CONSTANTS.INT24_MAX)), serial: v.pipe(v.number(), v.minValue(CONSTANTS.INT32_MIN), v.maxValue(CONSTANTS.INT32_MAX), v.integer()), smallint: v.pipe(v.number(), v.minValue(CONSTANTS.INT16_MIN), v.maxValue(CONSTANTS.INT16_MAX), v.integer()), smallserial: v.pipe(v.number(), v.minValue(CONSTANTS.INT16_MIN), v.maxValue(CONSTANTS.INT16_MAX), v.integer()), text1: v.string(), text2: v.enum({ a: 'a', b: 'b', c: 'c' }), sparsevec: v.string(), time: v.string(), timestamp1: v.date(), timestamp2: v.string(), uuid: v.pipe(v.string(), v.uuid()), varchar1: v.pipe(v.string(), v.maxLength(10 as number)), varchar2: v.enum({ a: 'a', b: 'b', c: 'c' }), vector: v.pipe(v.array(v.number()), v.length(3 as number)), array1: v.array(integerSchema), array2: v.pipe(v.array(v.array(integerSchema)), v.length(2 as number)), array3: v.pipe(v.array(v.array(v.pipe(v.string(), v.maxLength(10 as number)))), v.length(2 as number)), }); expectSchemaShape(t, expected).from(result); Expect>(); }); /* Infinitely recursive type */ { const TopLevelCondition: v.GenericSchema = v.custom(() => true); const table = pgTable('test', { json: json().$type().notNull(), jsonb: jsonb().$type(), }); const result = createSelectSchema(table); const expected = v.object({ json: TopLevelCondition, jsonb: v.nullable(TopLevelCondition), }); Expect, v.InferOutput>>(); } /* Disallow unknown keys in table refinement - select */ { const table = pgTable('test', { id: integer() }); // @ts-expect-error createSelectSchema(table, { unknown: v.string() }); } /* Disallow unknown keys in table refinement - insert */ { const table = pgTable('test', { id: integer() }); // @ts-expect-error createInsertSchema(table, { unknown: v.string() }); } /* Disallow unknown keys in table refinement - update */ { const table = pgTable('test', { id: integer() }); // @ts-expect-error createUpdateSchema(table, { unknown: v.string() }); } /* Disallow unknown keys in view qb - select */ { const table = pgTable('test', { id: integer() }); const view = pgView('test').as((qb) => qb.select().from(table)); const mView = pgMaterializedView('test').as((qb) => qb.select().from(table)); const nestedSelect = pgView('test').as((qb) => qb.select({ table }).from(table)); // @ts-expect-error createSelectSchema(view, { unknown: v.string() }); // @ts-expect-error createSelectSchema(mView, { unknown: v.string() }); // @ts-expect-error createSelectSchema(nestedSelect, { table: { unknown: v.string() } }); } /* Disallow unknown keys in view columns - select */ { const view = pgView('test', { id: integer() }).as(sql``); const mView = pgView('test', { id: integer() }).as(sql``); // @ts-expect-error createSelectSchema(view, { unknown: v.string() }); // @ts-expect-error createSelectSchema(mView, { unknown: v.string() }); }