From 10ba99f8b9a877a5e448fa8c761e272ae34752db Mon Sep 17 00:00:00 2001 From: OleksiiKH0240 Date: Fri, 27 Dec 2024 17:04:35 +0200 Subject: [PATCH 1/4] using 'with' with soft relations --- .gitignore | 5 +- drizzle-seed/package.json | 2 +- drizzle-seed/src/index.ts | 371 +++++++++++++++--- drizzle-seed/src/services/SeedService.ts | 13 +- drizzle-seed/src/types/tables.ts | 2 +- .../mysql/softRelationsTest/mysqlSchema.ts | 128 ++++++ .../softRelationsTest/softRelations.test.ts | 314 +++++++++++++++ .../pg/allDataTypesTest/drizzle.config.ts | 7 - drizzle-seed/tests/pg/drizzle.config.ts | 7 - .../tests/pg/generatorsTest/drizzle.config.ts | 7 - drizzle-seed/tests/pg/pgSchema.ts | 26 -- .../tests/pg/softRelationsTest/pgSchema.ts | 130 ++++++ .../softRelationsTest/softRelations.test.ts | 254 ++++++++++++ .../softRelationsTest/softRelations.test.ts | 253 ++++++++++++ .../sqlite/softRelationsTest/sqliteSchema.ts | 128 ++++++ drizzle-seed/tsconfig.json | 2 +- 16 files changed, 1538 insertions(+), 111 deletions(-) create mode 100644 drizzle-seed/tests/mysql/softRelationsTest/mysqlSchema.ts create mode 100644 drizzle-seed/tests/mysql/softRelationsTest/softRelations.test.ts delete mode 100644 drizzle-seed/tests/pg/allDataTypesTest/drizzle.config.ts delete mode 100644 drizzle-seed/tests/pg/drizzle.config.ts delete mode 100644 drizzle-seed/tests/pg/generatorsTest/drizzle.config.ts create mode 100644 drizzle-seed/tests/pg/softRelationsTest/pgSchema.ts create mode 100644 drizzle-seed/tests/pg/softRelationsTest/softRelations.test.ts create mode 100644 drizzle-seed/tests/sqlite/softRelationsTest/softRelations.test.ts create mode 100644 drizzle-seed/tests/sqlite/softRelationsTest/sqliteSchema.ts diff --git a/.gitignore b/.gitignore index c266f115f..97b7170df 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,4 @@ dist-dts rollup.config-*.mjs *.log .DS_Store -drizzle-seed/src/test.ts -drizzle-seed/src/testMysql.ts -drizzle-seed/src/testSqlite.ts -drizzle-seed/src/schemaTest.ts \ No newline at end of file +drizzle-seed/src/dev \ No newline at end of file diff --git a/drizzle-seed/package.json b/drizzle-seed/package.json index cbc3b570c..4954bcdb7 100644 --- a/drizzle-seed/package.json +++ b/drizzle-seed/package.json @@ -12,7 +12,7 @@ "generate-for-tests:mysql": "drizzle-kit generate --config=./src/tests/mysql/drizzle.config.ts", "generate-for-tests:sqlite": "drizzle-kit generate --config=./src/tests/sqlite/drizzle.config.ts", "generate": "drizzle-kit generate", - "start": "npx tsx ./src/test.ts", + "start": "npx tsx ./src/dev/test.ts", "start:pg": "npx tsx ./src/tests/northwind/pgTest.ts", "start:mysql": "npx tsx ./src/tests/northwind/mysqlTest.ts", "start:sqlite": "npx tsx ./src/tests/northwind/sqliteTest.ts", diff --git a/drizzle-seed/src/index.ts b/drizzle-seed/src/index.ts index c73e497cb..cc416c84d 100644 --- a/drizzle-seed/src/index.ts +++ b/drizzle-seed/src/index.ts @@ -1,5 +1,13 @@ /* eslint-disable drizzle-internal/require-entity-kind */ -import { getTableName, is, sql } from 'drizzle-orm'; +import { + createTableRelationsHelpers, + extractTablesRelationalConfig, + getTableName, + is, + One, + Relations, + sql, +} from 'drizzle-orm'; import type { MySqlColumn, MySqlSchema } from 'drizzle-orm/mysql-core'; import { getTableConfig as getMysqlTableConfig, MySqlDatabase, MySqlTable } from 'drizzle-orm/mysql-core'; @@ -23,7 +31,7 @@ type InferCallbackType< | MySqlDatabase | BaseSQLiteDatabase, SCHEMA extends { - [key: string]: PgTable | PgSchema | MySqlTable | MySqlSchema | SQLiteTable; + [key: string]: PgTable | PgSchema | MySqlTable | MySqlSchema | SQLiteTable | Relations; }, > = DB extends PgDatabase ? SCHEMA extends { [key: string]: @@ -31,7 +39,8 @@ type InferCallbackType< | PgSchema | MySqlTable | MySqlSchema - | SQLiteTable; + | SQLiteTable + | Relations; } ? { // iterates through schema fields. example -> schema: {"tableName": PgTable} [ @@ -63,7 +72,8 @@ type InferCallbackType< | PgSchema | MySqlTable | MySqlSchema - | SQLiteTable; + | SQLiteTable + | Relations; } ? { // iterates through schema fields. example -> schema: {"tableName": MySqlTable} [ @@ -95,7 +105,8 @@ type InferCallbackType< | PgSchema | MySqlTable | MySqlSchema - | SQLiteTable; + | SQLiteTable + | Relations; } ? { // iterates through schema fields. example -> schema: {"tableName": SQLiteTable} [ @@ -129,7 +140,7 @@ class SeedPromise< | MySqlDatabase | BaseSQLiteDatabase, SCHEMA extends { - [key: string]: PgTable | PgSchema | MySqlTable | MySqlSchema | SQLiteTable; + [key: string]: PgTable | PgSchema | MySqlTable | MySqlSchema | SQLiteTable | Relations; }, VERSION extends string | undefined, > implements Promise { @@ -344,6 +355,7 @@ export function seed< | MySqlTable | MySqlSchema | SQLiteTable + | Relations | any; }, VERSION extends '2' | '1' | undefined, @@ -360,6 +372,7 @@ const seedFunc = async ( | MySqlTable | MySqlSchema | SQLiteTable + | Relations | any; }, options: { count?: number; seed?: number; version?: string } = {}, @@ -371,17 +384,11 @@ const seedFunc = async ( } if (is(db, PgDatabase)) { - const { pgSchema } = filterPgTables(schema); - - await seedPostgres(db, pgSchema, { ...options, version }, refinements); + await seedPostgres(db, schema, { ...options, version }, refinements); } else if (is(db, MySqlDatabase)) { - const { mySqlSchema } = filterMySqlTables(schema); - - await seedMySql(db, mySqlSchema, { ...options, version }, refinements); + await seedMySql(db, schema, { ...options, version }, refinements); } else if (is(db, BaseSQLiteDatabase)) { - const { sqliteSchema } = filterSqliteTables(schema); - - await seedSqlite(db, sqliteSchema, { ...options, version }, refinements); + await seedSqlite(db, schema, { ...options, version }, refinements); } else { throw new Error( 'The drizzle-seed package currently supports only PostgreSQL, MySQL, and SQLite databases. Please ensure your database is one of these supported types', @@ -447,22 +454,22 @@ export async function reset< }, >(db: DB, schema: SCHEMA) { if (is(db, PgDatabase)) { - const { pgSchema } = filterPgTables(schema); + const { pgTables } = filterPgSchema(schema); - if (Object.entries(pgSchema).length > 0) { - await resetPostgres(db, pgSchema); + if (Object.entries(pgTables).length > 0) { + await resetPostgres(db, pgTables); } } else if (is(db, MySqlDatabase)) { - const { mySqlSchema } = filterMySqlTables(schema); + const { mysqlTables } = filterMysqlTables(schema); - if (Object.entries(mySqlSchema).length > 0) { - await resetMySql(db, mySqlSchema); + if (Object.entries(mysqlTables).length > 0) { + await resetMySql(db, mysqlTables); } } else if (is(db, BaseSQLiteDatabase)) { - const { sqliteSchema } = filterSqliteTables(schema); + const { sqliteTables } = filterSqliteTables(schema); - if (Object.entries(sqliteSchema).length > 0) { - await resetSqlite(db, sqliteSchema); + if (Object.entries(sqliteTables).length > 0) { + await resetSqlite(db, sqliteTables); } } else { throw new Error( @@ -474,9 +481,9 @@ export async function reset< // Postgres----------------------------------------------------------------------------------------------------------- const resetPostgres = async ( db: PgDatabase, - schema: { [key: string]: PgTable }, + pgTables: { [key: string]: PgTable }, ) => { - const tablesToTruncate = Object.entries(schema).map(([_, table]) => { + const tablesToTruncate = Object.entries(pgTables).map(([_, table]) => { const config = getPgTableConfig(table); config.schema = config.schema === undefined ? 'public' : config.schema; @@ -486,31 +493,49 @@ const resetPostgres = async ( await db.execute(sql.raw(`truncate ${tablesToTruncate.join(',')} cascade;`)); }; -const filterPgTables = (schema: { +const filterPgSchema = (schema: { [key: string]: | PgTable | PgSchema | MySqlTable | MySqlSchema | SQLiteTable + | Relations | any; }) => { const pgSchema = Object.fromEntries( + Object.entries(schema).filter((keyValue): keyValue is [string, PgTable | Relations] => + is(keyValue[1], PgTable) || is(keyValue[1], Relations) + ), + ); + + const pgTables = Object.fromEntries( Object.entries(schema).filter((keyValue): keyValue is [string, PgTable] => is(keyValue[1], PgTable)), ); - return { pgSchema }; + return { pgSchema, pgTables }; }; const seedPostgres = async ( db: PgDatabase, - schema: { [key: string]: PgTable }, + schema: { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | Relations + | any; + }, options: { count?: number; seed?: number; version?: number } = {}, refinements?: RefinementsType, ) => { const seedService = new SeedService(); - const { tables, relations } = getPostgresInfo(schema); + const { pgSchema, pgTables } = filterPgSchema(schema); + + const { tables, relations } = getPostgresInfo(pgSchema, pgTables); const generatedTablesGenerators = seedService.generatePossibleGenerators( 'postgresql', tables, @@ -525,7 +550,7 @@ const seedPostgres = async ( relations, generatedTablesGenerators, db, - schema, + pgTables, { ...options, preserveCyclicTablesData }, ); @@ -538,16 +563,19 @@ const seedPostgres = async ( relations, filteredTablesGenerators, db, - schema, + pgTables, { ...options, tablesValues, updateDataInDb, tablesUniqueNotNullColumn }, ); }; -const getPostgresInfo = (schema: { [key: string]: PgTable }) => { +const getPostgresInfo = ( + pgSchema: { [key: string]: PgTable | Relations }, + pgTables: { [key: string]: PgTable }, +) => { let tableConfig: ReturnType; let dbToTsColumnNamesMap: { [key: string]: string }; const dbToTsTableNamesMap: { [key: string]: string } = Object.fromEntries( - Object.entries(schema).map(([key, value]) => [getTableName(value), key]), + Object.entries(pgTables).map(([key, value]) => [getTableName(value), key]), ); const tables: Table[] = []; @@ -575,7 +603,65 @@ const getPostgresInfo = (schema: { [key: string]: PgTable }) => { return dbToTsColumnNamesMap; }; - for (const table of Object.values(schema)) { + const transformFromDrizzleRelation = ( + schema: Record, + getDbToTsColumnNamesMap: (table: PgTable) => { + [dbColName: string]: string; + }, + tableRelations: { + [tableName: string]: RelationWithReferences[]; + }, + ) => { + const schemaConfig = extractTablesRelationalConfig(schema, createTableRelationsHelpers); + const relations: RelationWithReferences[] = []; + for (const table of Object.values(schemaConfig.tables)) { + if (table.relations !== undefined) { + for (const drizzleRel of Object.values(table.relations)) { + if (is(drizzleRel, One)) { + const tableConfig = getPgTableConfig(drizzleRel.sourceTable as PgTable); + const tableDbSchema = tableConfig.schema ?? 'public'; + const tableDbName = tableConfig.name; + const tableTsName = schemaConfig.tableNamesMap[`${tableDbSchema}.${tableDbName}`] ?? tableDbName; + + const dbToTsColumnNamesMap = getDbToTsColumnNamesMap(drizzleRel.sourceTable); + const columns = drizzleRel.config?.fields.map((field) => dbToTsColumnNamesMap[field.name] as string) + ?? []; + + const refTableConfig = getPgTableConfig(drizzleRel.referencedTable as PgTable); + const refTableDbSchema = refTableConfig.schema ?? 'public'; + const refTableDbName = refTableConfig.name; + const refTableTsName = schemaConfig.tableNamesMap[`${refTableDbSchema}.${refTableDbName}`] + ?? refTableDbName; + + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap(drizzleRel.referencedTable); + const refColumns = drizzleRel.config?.references.map((ref) => + dbToTsColumnNamesMapForRefTable[ref.name] as string + ) + ?? []; + + if (tableRelations[refTableTsName] === undefined) { + tableRelations[refTableTsName] = []; + } + + const relation: RelationWithReferences = { + table: tableTsName, + columns, + refTable: refTableTsName, + refColumns, + refTableRels: tableRelations[refTableTsName], + type: 'one', + }; + + relations.push(relation); + tableRelations[tableTsName]!.push(relation); + } + } + } + } + return relations; + }; + + for (const table of Object.values(pgTables)) { tableConfig = getPgTableConfig(table); dbToTsColumnNamesMap = {}; @@ -707,6 +793,11 @@ const getPostgresInfo = (schema: { [key: string]: PgTable }) => { }); } + const transformedDrizzleRelations = transformFromDrizzleRelation(pgSchema, getDbToTsColumnNamesMap, tableRelations); + relations.push( + ...transformedDrizzleRelations, + ); + const isCyclicRelations = relations.map( (relI) => { // if (relations.some((relj) => relI.table === relj.refTable && relI.refTable === relj.table)) { @@ -777,7 +868,7 @@ const resetMySql = async ( await db.execute(sql.raw('SET FOREIGN_KEY_CHECKS = 1;')); }; -const filterMySqlTables = (schema: { +const filterMysqlTables = (schema: { [key: string]: | PgTable | PgSchema @@ -786,22 +877,39 @@ const filterMySqlTables = (schema: { | SQLiteTable | any; }) => { - const mySqlSchema = Object.fromEntries( + const mysqlSchema = Object.fromEntries( + Object.entries(schema).filter( + (keyValue): keyValue is [string, MySqlTable | Relations] => + is(keyValue[1], MySqlTable) || is(keyValue[1], Relations), + ), + ); + + const mysqlTables = Object.fromEntries( Object.entries(schema).filter( (keyValue): keyValue is [string, MySqlTable] => is(keyValue[1], MySqlTable), ), ); - return { mySqlSchema }; + return { mysqlSchema, mysqlTables }; }; const seedMySql = async ( db: MySqlDatabase, - schema: { [key: string]: MySqlTable }, + schema: { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | Relations + | any; + }, options: { count?: number; seed?: number; version?: number } = {}, refinements?: RefinementsType, ) => { - const { tables, relations } = getMySqlInfo(schema); + const { mysqlSchema, mysqlTables } = filterMysqlTables(schema); + const { tables, relations } = getMySqlInfo(mysqlSchema, mysqlTables); const seedService = new SeedService(); @@ -819,7 +927,7 @@ const seedMySql = async ( relations, generatedTablesGenerators, db, - schema, + mysqlTables, { ...options, preserveCyclicTablesData }, ); @@ -832,17 +940,20 @@ const seedMySql = async ( relations, filteredTablesGenerators, db, - schema, + mysqlTables, { ...options, tablesValues, updateDataInDb, tablesUniqueNotNullColumn }, ); }; -const getMySqlInfo = (schema: { [key: string]: MySqlTable }) => { +const getMySqlInfo = ( + mysqlSchema: { [key: string]: MySqlTable | Relations }, + mysqlTables: { [key: string]: MySqlTable }, +) => { let tableConfig: ReturnType; let dbToTsColumnNamesMap: { [key: string]: string }; const dbToTsTableNamesMap: { [key: string]: string } = Object.fromEntries( - Object.entries(schema).map(([key, value]) => [getTableName(value), key]), + Object.entries(mysqlTables).map(([key, value]) => [getTableName(value), key]), ); const tables: Table[] = []; @@ -870,7 +981,65 @@ const getMySqlInfo = (schema: { [key: string]: MySqlTable }) => { return dbToTsColumnNamesMap; }; - for (const table of Object.values(schema)) { + const transformFromDrizzleRelation = ( + schema: Record, + getDbToTsColumnNamesMap: (table: MySqlTable) => { + [dbColName: string]: string; + }, + tableRelations: { + [tableName: string]: RelationWithReferences[]; + }, + ) => { + const schemaConfig = extractTablesRelationalConfig(schema, createTableRelationsHelpers); + const relations: RelationWithReferences[] = []; + for (const table of Object.values(schemaConfig.tables)) { + if (table.relations !== undefined) { + for (const drizzleRel of Object.values(table.relations)) { + if (is(drizzleRel, One)) { + const tableConfig = getMysqlTableConfig(drizzleRel.sourceTable as MySqlTable); + const tableDbSchema = tableConfig.schema ?? 'public'; + const tableDbName = tableConfig.name; + const tableTsName = schemaConfig.tableNamesMap[`${tableDbSchema}.${tableDbName}`] ?? tableDbName; + + const dbToTsColumnNamesMap = getDbToTsColumnNamesMap(drizzleRel.sourceTable as MySqlTable); + const columns = drizzleRel.config?.fields.map((field) => dbToTsColumnNamesMap[field.name] as string) + ?? []; + + const refTableConfig = getMysqlTableConfig(drizzleRel.referencedTable as MySqlTable); + const refTableDbSchema = refTableConfig.schema ?? 'public'; + const refTableDbName = refTableConfig.name; + const refTableTsName = schemaConfig.tableNamesMap[`${refTableDbSchema}.${refTableDbName}`] + ?? refTableDbName; + + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap(drizzleRel.referencedTable as MySqlTable); + const refColumns = drizzleRel.config?.references.map((ref) => + dbToTsColumnNamesMapForRefTable[ref.name] as string + ) + ?? []; + + if (tableRelations[refTableTsName] === undefined) { + tableRelations[refTableTsName] = []; + } + + const relation: RelationWithReferences = { + table: tableTsName, + columns, + refTable: refTableTsName, + refColumns, + refTableRels: tableRelations[refTableTsName], + type: 'one', + }; + + relations.push(relation); + tableRelations[tableTsName]!.push(relation); + } + } + } + } + return relations; + }; + + for (const table of Object.values(mysqlTables)) { tableConfig = getMysqlTableConfig(table); dbToTsColumnNamesMap = {}; @@ -961,6 +1130,15 @@ const getMySqlInfo = (schema: { [key: string]: MySqlTable }) => { }); } + const transformedDrizzleRelations = transformFromDrizzleRelation( + mysqlSchema, + getDbToTsColumnNamesMap, + tableRelations, + ); + relations.push( + ...transformedDrizzleRelations, + ); + const isCyclicRelations = relations.map( (relI) => { const tableRel = tableRelations[relI.table]!.find((relJ) => relJ.refTable === relI.refTable)!; @@ -1006,21 +1184,39 @@ const filterSqliteTables = (schema: { | any; }) => { const sqliteSchema = Object.fromEntries( + Object.entries(schema).filter( + (keyValue): keyValue is [string, SQLiteTable | Relations] => + is(keyValue[1], SQLiteTable) || is(keyValue[1], Relations), + ), + ); + + const sqliteTables = Object.fromEntries( Object.entries(schema).filter( (keyValue): keyValue is [string, SQLiteTable] => is(keyValue[1], SQLiteTable), ), ); - return { sqliteSchema }; + return { sqliteSchema, sqliteTables }; }; const seedSqlite = async ( db: BaseSQLiteDatabase, - schema: { [key: string]: SQLiteTable }, + schema: { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SQLiteTable + | Relations + | any; + }, options: { count?: number; seed?: number; version?: number } = {}, refinements?: RefinementsType, ) => { - const { tables, relations } = getSqliteInfo(schema); + const { sqliteSchema, sqliteTables } = filterSqliteTables(schema); + + const { tables, relations } = getSqliteInfo(sqliteSchema, sqliteTables); const seedService = new SeedService(); @@ -1038,7 +1234,7 @@ const seedSqlite = async ( relations, generatedTablesGenerators, db, - schema, + sqliteTables, { ...options, preserveCyclicTablesData }, ); @@ -1051,16 +1247,19 @@ const seedSqlite = async ( relations, filteredTablesGenerators, db, - schema, + sqliteTables, { ...options, tablesValues, updateDataInDb, tablesUniqueNotNullColumn }, ); }; -const getSqliteInfo = (schema: { [key: string]: SQLiteTable }) => { +const getSqliteInfo = ( + sqliteSchema: { [key: string]: SQLiteTable | Relations }, + sqliteTables: { [key: string]: SQLiteTable }, +) => { let tableConfig: ReturnType; let dbToTsColumnNamesMap: { [key: string]: string }; const dbToTsTableNamesMap: { [key: string]: string } = Object.fromEntries( - Object.entries(schema).map(([key, value]) => [getTableName(value), key]), + Object.entries(sqliteTables).map(([key, value]) => [getTableName(value), key]), ); const tables: Table[] = []; @@ -1088,7 +1287,64 @@ const getSqliteInfo = (schema: { [key: string]: SQLiteTable }) => { return dbToTsColumnNamesMap; }; - for (const table of Object.values(schema)) { + const transformFromDrizzleRelation = ( + schema: Record, + getDbToTsColumnNamesMap: (table: SQLiteTable) => { + [dbColName: string]: string; + }, + tableRelations: { + [tableName: string]: RelationWithReferences[]; + }, + ) => { + const schemaConfig = extractTablesRelationalConfig(schema, createTableRelationsHelpers); + const relations: RelationWithReferences[] = []; + for (const table of Object.values(schemaConfig.tables)) { + if (table.relations !== undefined) { + for (const drizzleRel of Object.values(table.relations)) { + if (is(drizzleRel, One)) { + const tableConfig = getSqliteTableConfig(drizzleRel.sourceTable as SQLiteTable); + const tableDbName = tableConfig.name; + // TODO: tableNamesMap: have {public.customer: 'customer'} structure in sqlite + const tableTsName = schemaConfig.tableNamesMap[`public.${tableDbName}`] ?? tableDbName; + + const dbToTsColumnNamesMap = getDbToTsColumnNamesMap(drizzleRel.sourceTable as SQLiteTable); + const columns = drizzleRel.config?.fields.map((field) => dbToTsColumnNamesMap[field.name] as string) + ?? []; + + const refTableConfig = getSqliteTableConfig(drizzleRel.referencedTable as SQLiteTable); + const refTableDbName = refTableConfig.name; + const refTableTsName = schemaConfig.tableNamesMap[`public.${refTableDbName}`] + ?? refTableDbName; + + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap(drizzleRel.referencedTable as SQLiteTable); + const refColumns = drizzleRel.config?.references.map((ref) => + dbToTsColumnNamesMapForRefTable[ref.name] as string + ) + ?? []; + + if (tableRelations[refTableTsName] === undefined) { + tableRelations[refTableTsName] = []; + } + + const relation: RelationWithReferences = { + table: tableTsName, + columns, + refTable: refTableTsName, + refColumns, + refTableRels: tableRelations[refTableTsName], + type: 'one', + }; + + relations.push(relation); + tableRelations[tableTsName]!.push(relation); + } + } + } + } + return relations; + }; + + for (const table of Object.values(sqliteTables)) { tableConfig = getSqliteTableConfig(table); dbToTsColumnNamesMap = {}; @@ -1176,6 +1432,15 @@ const getSqliteInfo = (schema: { [key: string]: SQLiteTable }) => { }); } + const transformedDrizzleRelations = transformFromDrizzleRelation( + sqliteSchema, + getDbToTsColumnNamesMap, + tableRelations, + ); + relations.push( + ...transformedDrizzleRelations, + ); + const isCyclicRelations = relations.map( (relI) => { const tableRel = tableRelations[relI.table]!.find((relJ) => relJ.refTable === relI.refTable)!; diff --git a/drizzle-seed/src/services/SeedService.ts b/drizzle-seed/src/services/SeedService.ts index 1370138b4..14550ae9b 100644 --- a/drizzle-seed/src/services/SeedService.ts +++ b/drizzle-seed/src/services/SeedService.ts @@ -110,9 +110,10 @@ export class SeedService { if (!tablesInOutRelations[table.name]?.dependantTableNames.has(fkTableName)) { const reason = tablesInOutRelations[table.name]?.selfRelation === true ? `"${table.name}" table has self reference` - : `"${fkTableName}" table doesn't have reference to "${table.name}" table`; + : `"${fkTableName}" table doesn't have reference to "${table.name}" table or` + + `\n you didn't include your one-to-many relation in the seed function schema`; throw new Error( - `${reason}. you can't specify "${fkTableName}" as parameter in ${table.name}.with object.`, + `${reason}.` + `\nYou can't specify "${fkTableName}" as parameter in ${table.name}.with object.`, ); } @@ -212,7 +213,9 @@ export class SeedService { if (foreignKeyColumns[col.name]?.table === undefined && col.notNull === true) { throw new Error( - `Column '${col.name}' has no null contraint, and you didn't specify a table for foreign key on column '${col.name}' in '${table.name}' table. You should pass `, + `Column '${col.name}' has not null contraint,` + + `\nand you didn't specify a table for foreign key on column '${col.name}' in '${table.name}' table.` + + `\n For more details, check this: https://orm.drizzle.team/docs/guides/seeding-with-partially-exposed-tables#example-1`, ); } @@ -223,7 +226,9 @@ export class SeedService { if (foreignKeyColumns[col.name]?.table === undefined && col.notNull === false) { console.warn( `Column '${col.name}' in '${table.name}' table will be filled with Null values` - + `\nbecause you specified neither a table for foreign key on column '${col.name}' nor a function for '${col.name}' column in refinements.`, + + `\nbecause you specified neither a table for foreign key on column '${col.name}'` + + `\nnor a function for '${col.name}' column in refinements.` + + `\nFor more details, check this: https://orm.drizzle.team/docs/guides/seeding-with-partially-exposed-tables#example-2`, ); } columnPossibleGenerator.generator = new generatorsMap.GenerateDefault[0]({ defaultValue: null }); diff --git a/drizzle-seed/src/types/tables.ts b/drizzle-seed/src/types/tables.ts index dc28c748d..2fadd23f0 100644 --- a/drizzle-seed/src/types/tables.ts +++ b/drizzle-seed/src/types/tables.ts @@ -29,7 +29,7 @@ export type Table = { export type Relation = { // name: string; - // type: "one" | "many"; + type?: 'one' | 'many'; table: string; // schema: string; columns: string[]; diff --git a/drizzle-seed/tests/mysql/softRelationsTest/mysqlSchema.ts b/drizzle-seed/tests/mysql/softRelationsTest/mysqlSchema.ts new file mode 100644 index 000000000..7f0fc17df --- /dev/null +++ b/drizzle-seed/tests/mysql/softRelationsTest/mysqlSchema.ts @@ -0,0 +1,128 @@ +import { relations } from 'drizzle-orm'; +import { float, int, mysqlTable, text, timestamp, varchar } from 'drizzle-orm/mysql-core'; + +export const customers = mysqlTable('customer', { + id: varchar('id', { length: 256 }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = mysqlTable( + 'employee', + { + id: int('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: timestamp('birth_date').notNull(), + hireDate: timestamp('hire_date').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: int('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: int('reports_to'), + photoPath: text('photo_path'), + }, +); + +export const employeesRelations = relations(employees, ({ one }) => ({ + employee: one(employees, { + fields: [employees.reportsTo], + references: [employees.id], + }), +})); + +export const orders = mysqlTable('order', { + id: int('id').primaryKey(), + orderDate: timestamp('order_date').notNull(), + requiredDate: timestamp('required_date').notNull(), + shippedDate: timestamp('shipped_date'), + shipVia: int('ship_via').notNull(), + freight: float('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: varchar('customer_id', { length: 256 }).notNull(), + + employeeId: int('employee_id').notNull(), +}); + +export const ordersRelations = relations(orders, ({ one }) => ({ + customer: one(customers, { + fields: [orders.customerId], + references: [customers.id], + }), + employee: one(employees, { + fields: [orders.employeeId], + references: [employees.id], + }), +})); + +export const suppliers = mysqlTable('supplier', { + id: int('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = mysqlTable('product', { + id: int('id').primaryKey(), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: float('unit_price').notNull(), + unitsInStock: int('units_in_stock').notNull(), + unitsOnOrder: int('units_on_order').notNull(), + reorderLevel: int('reorder_level').notNull(), + discontinued: int('discontinued').notNull(), + + supplierId: int('supplier_id').notNull(), +}); + +export const productsRelations = relations(products, ({ one }) => ({ + supplier: one(suppliers, { + fields: [products.supplierId], + references: [suppliers.id], + }), +})); + +export const details = mysqlTable('order_detail', { + unitPrice: float('unit_price').notNull(), + quantity: int('quantity').notNull(), + discount: float('discount').notNull(), + + orderId: int('order_id').notNull(), + + productId: int('product_id').notNull(), +}); + +export const detailsRelations = relations(details, ({ one }) => ({ + order: one(orders, { + fields: [details.orderId], + references: [orders.id], + }), + product: one(products, { + fields: [details.productId], + references: [products.id], + }), +})); diff --git a/drizzle-seed/tests/mysql/softRelationsTest/softRelations.test.ts b/drizzle-seed/tests/mysql/softRelationsTest/softRelations.test.ts new file mode 100644 index 000000000..7f61b80eb --- /dev/null +++ b/drizzle-seed/tests/mysql/softRelationsTest/softRelations.test.ts @@ -0,0 +1,314 @@ +import Docker from 'dockerode'; +import { sql } from 'drizzle-orm'; +import type { MySql2Database } from 'drizzle-orm/mysql2'; +import { drizzle } from 'drizzle-orm/mysql2'; +import getPort from 'get-port'; +import type { Connection } from 'mysql2/promise'; +import { createConnection } from 'mysql2/promise'; +import { v4 as uuid } from 'uuid'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import { reset, seed } from '../../../src/index.ts'; +import * as schema from './mysqlSchema.ts'; + +let mysqlContainer: Docker.Container; +let client: Connection; +let db: MySql2Database; + +async function createDockerDB(): Promise { + const docker = new Docker(); + const port = await getPort({ port: 3306 }); + const image = 'mysql:8'; + + const pullStream = await docker.pull(image); + await new Promise((resolve, reject) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err)) + ); + + mysqlContainer = await docker.createContainer({ + Image: image, + Env: ['MYSQL_ROOT_PASSWORD=mysql', 'MYSQL_DATABASE=drizzle'], + name: `drizzle-integration-tests-${uuid()}`, + HostConfig: { + AutoRemove: true, + PortBindings: { + '3306/tcp': [{ HostPort: `${port}` }], + }, + }, + }); + + await mysqlContainer.start(); + + return `mysql://root:mysql@127.0.0.1:${port}/drizzle`; +} + +beforeAll(async () => { + const connectionString = await createDockerDB(); + + const sleep = 1000; + let timeLeft = 40000; + let connected = false; + let lastError: unknown | undefined; + do { + try { + client = await createConnection(connectionString); + await client.connect(); + db = drizzle(client); + connected = true; + break; + } catch (e) { + lastError = e; + await new Promise((resolve) => setTimeout(resolve, sleep)); + timeLeft -= sleep; + } + } while (timeLeft > 0); + if (!connected) { + console.error('Cannot connect to MySQL'); + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); + throw lastError; + } + + await db.execute( + sql` + CREATE TABLE \`customer\` ( + \`id\` varchar(256) NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text, + \`region\` text, + \`country\` text NOT NULL, + \`phone\` text NOT NULL, + \`fax\` text, + CONSTRAINT \`customer_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`order_detail\` ( + \`unit_price\` float NOT NULL, + \`quantity\` int NOT NULL, + \`discount\` float NOT NULL, + \`order_id\` int NOT NULL, + \`product_id\` int NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`employee\` ( + \`id\` int NOT NULL, + \`last_name\` text NOT NULL, + \`first_name\` text, + \`title\` text NOT NULL, + \`title_of_courtesy\` text NOT NULL, + \`birth_date\` timestamp NOT NULL, + \`hire_date\` timestamp NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`home_phone\` text NOT NULL, + \`extension\` int NOT NULL, + \`notes\` text NOT NULL, + \`reports_to\` int, + \`photo_path\` text, + CONSTRAINT \`employee_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`order\` ( + \`id\` int NOT NULL, + \`order_date\` timestamp NOT NULL, + \`required_date\` timestamp NOT NULL, + \`shipped_date\` timestamp, + \`ship_via\` int NOT NULL, + \`freight\` float NOT NULL, + \`ship_name\` text NOT NULL, + \`ship_city\` text NOT NULL, + \`ship_region\` text, + \`ship_postal_code\` text, + \`ship_country\` text NOT NULL, + \`customer_id\` varchar(256) NOT NULL, + \`employee_id\` int NOT NULL, + CONSTRAINT \`order_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`product\` ( + \`id\` int NOT NULL, + \`name\` text NOT NULL, + \`quantity_per_unit\` text NOT NULL, + \`unit_price\` float NOT NULL, + \`units_in_stock\` int NOT NULL, + \`units_on_order\` int NOT NULL, + \`reorder_level\` int NOT NULL, + \`discontinued\` int NOT NULL, + \`supplier_id\` int NOT NULL, + CONSTRAINT \`product_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`supplier\` ( + \`id\` int NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`region\` text, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`phone\` text NOT NULL, + CONSTRAINT \`supplier_id\` PRIMARY KEY(\`id\`) + ); + `, + ); +}); + +afterAll(async () => { + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +const checkSoftRelations = ( + customers: (typeof schema.customers.$inferSelect)[], + details: (typeof schema.details.$inferSelect)[], + employees: (typeof schema.employees.$inferSelect)[], + orders: (typeof schema.orders.$inferSelect)[], + products: (typeof schema.products.$inferSelect)[], + suppliers: (typeof schema.suppliers.$inferSelect)[], +) => { + // employees soft relations check + const employeeIds = new Set(employees.map((employee) => employee.id)); + const employeesPredicate = employees.every((employee) => + employee.reportsTo !== null && employeeIds.has(employee.reportsTo) + ); + expect(employeesPredicate).toBe(true); + + // orders soft relations check + const customerIds = new Set(customers.map((customer) => customer.id)); + const ordersPredicate1 = orders.every((order) => order.customerId !== null && customerIds.has(order.customerId)); + expect(ordersPredicate1).toBe(true); + + const ordersPredicate2 = orders.every((order) => order.employeeId !== null && employeeIds.has(order.employeeId)); + expect(ordersPredicate2).toBe(true); + + // product soft relations check + const supplierIds = new Set(suppliers.map((supplier) => supplier.id)); + const productsPredicate = products.every((product) => + product.supplierId !== null && supplierIds.has(product.supplierId) + ); + expect(productsPredicate).toBe(true); + + // details soft relations check + const orderIds = new Set(orders.map((order) => order.id)); + const detailsPredicate1 = details.every((detail) => detail.orderId !== null && orderIds.has(detail.orderId)); + expect(detailsPredicate1).toBe(true); + + const productIds = new Set(products.map((product) => product.id)); + const detailsPredicate2 = details.every((detail) => detail.productId !== null && productIds.has(detail.productId)); + expect(detailsPredicate2).toBe(true); +}; + +test('basic seed, soft relations test', async () => { + await seed(db, schema); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(10); + expect(details.length).toBe(10); + expect(employees.length).toBe(10); + expect(orders.length).toBe(10); + expect(products.length).toBe(10); + expect(suppliers.length).toBe(10); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); + +test("redefine(refine) orders count using 'with' in customers, soft relations test", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 13, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); + +test("sequential using of 'with', soft relations test", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 12, + with: { + details: 3, + }, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(24); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); diff --git a/drizzle-seed/tests/pg/allDataTypesTest/drizzle.config.ts b/drizzle-seed/tests/pg/allDataTypesTest/drizzle.config.ts deleted file mode 100644 index 131b4d025..000000000 --- a/drizzle-seed/tests/pg/allDataTypesTest/drizzle.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'drizzle-kit'; - -export default defineConfig({ - schema: './src/tests/pg/allDataTypesTest/pgSchema.ts', - out: './src/tests/pg/allDataTypesTest/pgMigrations', - dialect: 'postgresql', -}); diff --git a/drizzle-seed/tests/pg/drizzle.config.ts b/drizzle-seed/tests/pg/drizzle.config.ts deleted file mode 100644 index 65b65236f..000000000 --- a/drizzle-seed/tests/pg/drizzle.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'drizzle-kit'; - -export default defineConfig({ - schema: './src/tests/pg/pgSchema.ts', - out: './src/tests/pg/pgMigrations', - dialect: 'postgresql', -}); diff --git a/drizzle-seed/tests/pg/generatorsTest/drizzle.config.ts b/drizzle-seed/tests/pg/generatorsTest/drizzle.config.ts deleted file mode 100644 index 30331986c..000000000 --- a/drizzle-seed/tests/pg/generatorsTest/drizzle.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'drizzle-kit'; - -export default defineConfig({ - schema: './src/tests/pg/generatorsTest/pgSchema.ts', - out: './src/tests/pg/generatorsTest/pgMigrations', - dialect: 'postgresql', -}); diff --git a/drizzle-seed/tests/pg/pgSchema.ts b/drizzle-seed/tests/pg/pgSchema.ts index f6f4f8347..1a9af755e 100644 --- a/drizzle-seed/tests/pg/pgSchema.ts +++ b/drizzle-seed/tests/pg/pgSchema.ts @@ -1,29 +1,3 @@ -// import { serial, integer, varchar, pgSchema, getTableConfig as getPgTableConfig } from "drizzle-orm/pg-core"; - -// export const schema = pgSchema("seeder_lib_pg"); - -// export const users = schema.table("users", { -// id: serial("id").primaryKey(), -// name: varchar("name", { length: 256 }), -// email: varchar("email", { length: 256 }), -// phone: varchar("phone", { length: 256 }), -// password: varchar("password", { length: 256 }) -// }); - -// export const posts = schema.table("posts", { -// id: serial("id").primaryKey(), -// title: varchar("title", { length: 256 }), -// content: varchar("content", { length: 256 }), -// userId: integer("user_id").references(() => users.id) -// }); - -// export const comments = schema.table("comments", { -// id: serial("id").primaryKey(), -// content: varchar("content", { length: 256 }), -// postId: integer("post_id").references(() => posts.id), -// userId: integer("user_id").references(() => users.id) -// }); - import type { AnyPgColumn } from 'drizzle-orm/pg-core'; import { integer, numeric, pgSchema, serial, text, timestamp, varchar } from 'drizzle-orm/pg-core'; diff --git a/drizzle-seed/tests/pg/softRelationsTest/pgSchema.ts b/drizzle-seed/tests/pg/softRelationsTest/pgSchema.ts new file mode 100644 index 000000000..357ea23cf --- /dev/null +++ b/drizzle-seed/tests/pg/softRelationsTest/pgSchema.ts @@ -0,0 +1,130 @@ +import { relations } from 'drizzle-orm'; +import { integer, numeric, pgSchema, text, timestamp, varchar } from 'drizzle-orm/pg-core'; + +export const schema = pgSchema('seeder_lib_pg'); + +export const customers = schema.table('customer', { + id: varchar('id', { length: 256 }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = schema.table( + 'employee', + { + id: integer('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: timestamp('birth_date').notNull(), + hireDate: timestamp('hire_date').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: integer('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: integer('reports_to'), + photoPath: text('photo_path'), + }, +); + +export const employeesRelations = relations(employees, ({ one }) => ({ + employee: one(employees, { + fields: [employees.reportsTo], + references: [employees.id], + }), +})); + +export const orders = schema.table('order', { + id: integer('id').primaryKey(), + orderDate: timestamp('order_date').notNull(), + requiredDate: timestamp('required_date').notNull(), + shippedDate: timestamp('shipped_date'), + shipVia: integer('ship_via').notNull(), + freight: numeric('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: text('customer_id').notNull(), + + employeeId: integer('employee_id').notNull(), +}); + +export const ordersRelations = relations(orders, ({ one }) => ({ + customer: one(customers, { + fields: [orders.customerId], + references: [customers.id], + }), + employee: one(employees, { + fields: [orders.employeeId], + references: [employees.id], + }), +})); + +export const suppliers = schema.table('supplier', { + id: integer('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = schema.table('product', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: numeric('unit_price').notNull(), + unitsInStock: integer('units_in_stock').notNull(), + unitsOnOrder: integer('units_on_order').notNull(), + reorderLevel: integer('reorder_level').notNull(), + discontinued: integer('discontinued').notNull(), + + supplierId: integer('supplier_id').notNull(), +}); + +export const productsRelations = relations(products, ({ one }) => ({ + supplier: one(suppliers, { + fields: [products.supplierId], + references: [suppliers.id], + }), +})); + +export const details = schema.table('order_detail', { + unitPrice: numeric('unit_price').notNull(), + quantity: integer('quantity').notNull(), + discount: numeric('discount').notNull(), + + orderId: integer('order_id').notNull(), + + productId: integer('product_id').notNull(), +}); + +export const detailsRelations = relations(details, ({ one }) => ({ + order: one(orders, { + fields: [details.orderId], + references: [orders.id], + }), + product: one(products, { + fields: [details.productId], + references: [products.id], + }), +})); diff --git a/drizzle-seed/tests/pg/softRelationsTest/softRelations.test.ts b/drizzle-seed/tests/pg/softRelationsTest/softRelations.test.ts new file mode 100644 index 000000000..205647812 --- /dev/null +++ b/drizzle-seed/tests/pg/softRelationsTest/softRelations.test.ts @@ -0,0 +1,254 @@ +import { PGlite } from '@electric-sql/pglite'; +import { sql } from 'drizzle-orm'; +import type { PgliteDatabase } from 'drizzle-orm/pglite'; +import { drizzle } from 'drizzle-orm/pglite'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import { reset, seed } from '../../../src/index.ts'; +import * as schema from './pgSchema.ts'; + +let client: PGlite; +let db: PgliteDatabase; + +beforeAll(async () => { + client = new PGlite(); + + db = drizzle(client); + + await db.execute(sql`CREATE SCHEMA "seeder_lib_pg";`); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."customer" ( + "id" varchar(256) PRIMARY KEY NOT NULL, + "company_name" text NOT NULL, + "contact_name" text NOT NULL, + "contact_title" text NOT NULL, + "address" text NOT NULL, + "city" text NOT NULL, + "postal_code" text, + "region" text, + "country" text NOT NULL, + "phone" text NOT NULL, + "fax" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."order_detail" ( + "unit_price" numeric NOT NULL, + "quantity" integer NOT NULL, + "discount" numeric NOT NULL, + "order_id" integer NOT NULL, + "product_id" integer NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."employee" ( + "id" integer PRIMARY KEY NOT NULL, + "last_name" text NOT NULL, + "first_name" text, + "title" text NOT NULL, + "title_of_courtesy" text NOT NULL, + "birth_date" timestamp NOT NULL, + "hire_date" timestamp NOT NULL, + "address" text NOT NULL, + "city" text NOT NULL, + "postal_code" text NOT NULL, + "country" text NOT NULL, + "home_phone" text NOT NULL, + "extension" integer NOT NULL, + "notes" text NOT NULL, + "reports_to" integer, + "photo_path" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."order" ( + "id" integer PRIMARY KEY NOT NULL, + "order_date" timestamp NOT NULL, + "required_date" timestamp NOT NULL, + "shipped_date" timestamp, + "ship_via" integer NOT NULL, + "freight" numeric NOT NULL, + "ship_name" text NOT NULL, + "ship_city" text NOT NULL, + "ship_region" text, + "ship_postal_code" text, + "ship_country" text NOT NULL, + "customer_id" text NOT NULL, + "employee_id" integer NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."product" ( + "id" integer PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "quantity_per_unit" text NOT NULL, + "unit_price" numeric NOT NULL, + "units_in_stock" integer NOT NULL, + "units_on_order" integer NOT NULL, + "reorder_level" integer NOT NULL, + "discontinued" integer NOT NULL, + "supplier_id" integer NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."supplier" ( + "id" integer PRIMARY KEY NOT NULL, + "company_name" text NOT NULL, + "contact_name" text NOT NULL, + "contact_title" text NOT NULL, + "address" text NOT NULL, + "city" text NOT NULL, + "region" text, + "postal_code" text NOT NULL, + "country" text NOT NULL, + "phone" text NOT NULL + ); + `, + ); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +afterAll(async () => { + await client.close(); +}); + +const checkSoftRelations = ( + customers: (typeof schema.customers.$inferSelect)[], + details: (typeof schema.details.$inferSelect)[], + employees: (typeof schema.employees.$inferSelect)[], + orders: (typeof schema.orders.$inferSelect)[], + products: (typeof schema.products.$inferSelect)[], + suppliers: (typeof schema.suppliers.$inferSelect)[], +) => { + // employees soft relations check + const employeeIds = new Set(employees.map((employee) => employee.id)); + const employeesPredicate = employees.every((employee) => + employee.reportsTo !== null && employeeIds.has(employee.reportsTo) + ); + expect(employeesPredicate).toBe(true); + + // orders soft relations check + const customerIds = new Set(customers.map((customer) => customer.id)); + const ordersPredicate1 = orders.every((order) => order.customerId !== null && customerIds.has(order.customerId)); + expect(ordersPredicate1).toBe(true); + + const ordersPredicate2 = orders.every((order) => order.employeeId !== null && employeeIds.has(order.employeeId)); + expect(ordersPredicate2).toBe(true); + + // product soft relations check + const supplierIds = new Set(suppliers.map((supplier) => supplier.id)); + const productsPredicate = products.every((product) => + product.supplierId !== null && supplierIds.has(product.supplierId) + ); + expect(productsPredicate).toBe(true); + + // details soft relations check + const orderIds = new Set(orders.map((order) => order.id)); + const detailsPredicate1 = details.every((detail) => detail.orderId !== null && orderIds.has(detail.orderId)); + expect(detailsPredicate1).toBe(true); + + const productIds = new Set(products.map((product) => product.id)); + const detailsPredicate2 = details.every((detail) => detail.productId !== null && productIds.has(detail.productId)); + expect(detailsPredicate2).toBe(true); +}; + +test('basic seed, soft relations test', async () => { + await seed(db, schema); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(10); + expect(details.length).toBe(10); + expect(employees.length).toBe(10); + expect(orders.length).toBe(10); + expect(products.length).toBe(10); + expect(suppliers.length).toBe(10); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); + +test("redefine(refine) orders count using 'with' in customers, soft relations test", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 13, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); + +test("sequential using of 'with', soft relations test", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 12, + with: { + details: 3, + }, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(24); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); diff --git a/drizzle-seed/tests/sqlite/softRelationsTest/softRelations.test.ts b/drizzle-seed/tests/sqlite/softRelationsTest/softRelations.test.ts new file mode 100644 index 000000000..124ac8ee1 --- /dev/null +++ b/drizzle-seed/tests/sqlite/softRelationsTest/softRelations.test.ts @@ -0,0 +1,253 @@ +import BetterSqlite3 from 'better-sqlite3'; +import { sql } from 'drizzle-orm'; +import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import { reset, seed } from '../../../src/index.ts'; +import * as schema from './sqliteSchema.ts'; + +let client: BetterSqlite3.Database; +let db: BetterSQLite3Database; + +beforeAll(async () => { + client = new BetterSqlite3(':memory:'); + + db = drizzle(client); + + db.run( + sql.raw(` + CREATE TABLE \`customer\` ( + \`id\` text PRIMARY KEY NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text, + \`region\` text, + \`country\` text NOT NULL, + \`phone\` text NOT NULL, + \`fax\` text +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`order_detail\` ( + \`unit_price\` numeric NOT NULL, + \`quantity\` integer NOT NULL, + \`discount\` numeric NOT NULL, + \`order_id\` integer NOT NULL, + \`product_id\` integer NOT NULL +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`employee\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`last_name\` text NOT NULL, + \`first_name\` text, + \`title\` text NOT NULL, + \`title_of_courtesy\` text NOT NULL, + \`birth_date\` integer NOT NULL, + \`hire_date\` integer NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`home_phone\` text NOT NULL, + \`extension\` integer NOT NULL, + \`notes\` text NOT NULL, + \`reports_to\` integer, + \`photo_path\` text +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`order\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`order_date\` integer NOT NULL, + \`required_date\` integer NOT NULL, + \`shipped_date\` integer, + \`ship_via\` integer NOT NULL, + \`freight\` numeric NOT NULL, + \`ship_name\` text NOT NULL, + \`ship_city\` text NOT NULL, + \`ship_region\` text, + \`ship_postal_code\` text, + \`ship_country\` text NOT NULL, + \`customer_id\` text NOT NULL, + \`employee_id\` integer NOT NULL +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`product\` ( + \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + \`name\` text NOT NULL, + \`quantity_per_unit\` text NOT NULL, + \`unit_price\` numeric NOT NULL, + \`units_in_stock\` integer NOT NULL, + \`units_on_order\` integer NOT NULL, + \`reorder_level\` integer NOT NULL, + \`discontinued\` integer NOT NULL, + \`supplier_id\` integer NOT NULL +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`supplier\` ( + \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + \`company_name\` text NOT NULL, + \`contact_name\` text NOT NULL, + \`contact_title\` text NOT NULL, + \`address\` text NOT NULL, + \`city\` text NOT NULL, + \`region\` text, + \`postal_code\` text NOT NULL, + \`country\` text NOT NULL, + \`phone\` text NOT NULL +); + `), + ); +}); + +afterAll(async () => { + client.close(); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +const checkSoftRelations = ( + customers: (typeof schema.customers.$inferSelect)[], + details: (typeof schema.details.$inferSelect)[], + employees: (typeof schema.employees.$inferSelect)[], + orders: (typeof schema.orders.$inferSelect)[], + products: (typeof schema.products.$inferSelect)[], + suppliers: (typeof schema.suppliers.$inferSelect)[], +) => { + // employees soft relations check + const employeeIds = new Set(employees.map((employee) => employee.id)); + const employeesPredicate = employees.every((employee) => + employee.reportsTo !== null && employeeIds.has(employee.reportsTo) + ); + expect(employeesPredicate).toBe(true); + + // orders soft relations check + const customerIds = new Set(customers.map((customer) => customer.id)); + const ordersPredicate1 = orders.every((order) => order.customerId !== null && customerIds.has(order.customerId)); + expect(ordersPredicate1).toBe(true); + + const ordersPredicate2 = orders.every((order) => order.employeeId !== null && employeeIds.has(order.employeeId)); + expect(ordersPredicate2).toBe(true); + + // product soft relations check + const supplierIds = new Set(suppliers.map((supplier) => supplier.id)); + const productsPredicate = products.every((product) => + product.supplierId !== null && supplierIds.has(product.supplierId) + ); + expect(productsPredicate).toBe(true); + + // details soft relations check + const orderIds = new Set(orders.map((order) => order.id)); + const detailsPredicate1 = details.every((detail) => detail.orderId !== null && orderIds.has(detail.orderId)); + expect(detailsPredicate1).toBe(true); + + const productIds = new Set(products.map((product) => product.id)); + const detailsPredicate2 = details.every((detail) => detail.productId !== null && productIds.has(detail.productId)); + expect(detailsPredicate2).toBe(true); +}; + +test('basic seed, soft relations test', async () => { + await seed(db, schema); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(10); + expect(details.length).toBe(10); + expect(employees.length).toBe(10); + expect(orders.length).toBe(10); + expect(products.length).toBe(10); + expect(suppliers.length).toBe(10); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); + +test("redefine(refine) orders count using 'with' in customers, soft relations test", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 13, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); + +test("sequential using of 'with', soft relations test", async () => { + await seed(db, schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 12, + with: { + details: 3, + }, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(24); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); + + checkSoftRelations(customers, details, employees, orders, products, suppliers); +}); diff --git a/drizzle-seed/tests/sqlite/softRelationsTest/sqliteSchema.ts b/drizzle-seed/tests/sqlite/softRelationsTest/sqliteSchema.ts new file mode 100644 index 000000000..75572c63a --- /dev/null +++ b/drizzle-seed/tests/sqlite/softRelationsTest/sqliteSchema.ts @@ -0,0 +1,128 @@ +import { relations } from 'drizzle-orm'; +import { integer, numeric, sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +export const customers = sqliteTable('customer', { + id: text('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = sqliteTable( + 'employee', + { + id: integer('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: integer('birth_date', { mode: 'timestamp' }).notNull(), + hireDate: integer('hire_date', { mode: 'timestamp' }).notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: integer('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: integer('reports_to'), + photoPath: text('photo_path'), + }, +); + +export const employeesRelations = relations(employees, ({ one }) => ({ + employee: one(employees, { + fields: [employees.reportsTo], + references: [employees.id], + }), +})); + +export const orders = sqliteTable('order', { + id: integer('id').primaryKey(), + orderDate: integer('order_date', { mode: 'timestamp' }).notNull(), + requiredDate: integer('required_date', { mode: 'timestamp' }).notNull(), + shippedDate: integer('shipped_date', { mode: 'timestamp' }), + shipVia: integer('ship_via').notNull(), + freight: numeric('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: text('customer_id').notNull(), + + employeeId: integer('employee_id').notNull(), +}); + +export const ordersRelations = relations(orders, ({ one }) => ({ + customer: one(customers, { + fields: [orders.customerId], + references: [customers.id], + }), + employee: one(employees, { + fields: [orders.employeeId], + references: [employees.id], + }), +})); + +export const suppliers = sqliteTable('supplier', { + id: integer('id').primaryKey({ autoIncrement: true }), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = sqliteTable('product', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: numeric('unit_price').notNull(), + unitsInStock: integer('units_in_stock').notNull(), + unitsOnOrder: integer('units_on_order').notNull(), + reorderLevel: integer('reorder_level').notNull(), + discontinued: integer('discontinued').notNull(), + + supplierId: integer('supplier_id').notNull(), +}); + +export const productsRelations = relations(products, ({ one }) => ({ + supplier: one(suppliers, { + fields: [products.supplierId], + references: [suppliers.id], + }), +})); + +export const details = sqliteTable('order_detail', { + unitPrice: numeric('unit_price').notNull(), + quantity: integer('quantity').notNull(), + discount: numeric('discount').notNull(), + + orderId: integer('order_id').notNull(), + + productId: integer('product_id').notNull(), +}); + +export const detailsRelations = relations(details, ({ one }) => ({ + order: one(orders, { + fields: [details.orderId], + references: [orders.id], + }), + product: one(products, { + fields: [details.productId], + references: [products.id], + }), +})); diff --git a/drizzle-seed/tsconfig.json b/drizzle-seed/tsconfig.json index 222e6a4dd..f32902e10 100644 --- a/drizzle-seed/tsconfig.json +++ b/drizzle-seed/tsconfig.json @@ -43,6 +43,6 @@ "~/*": ["src/*"] } }, - "exclude": ["**/dist", "src/schemaTest.ts", "src/test.ts"], + "exclude": ["**/dist", "src/dev"], "include": ["src", "*.ts", "tests"] } From d8377e5bfc3f656b617edb4ee5401da66e049809 Mon Sep 17 00:00:00 2001 From: OleksiiKH0240 Date: Mon, 30 Dec 2024 13:08:24 +0200 Subject: [PATCH 2/4] fixes --- drizzle-seed/src/services/SeedService.ts | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/drizzle-seed/src/services/SeedService.ts b/drizzle-seed/src/services/SeedService.ts index 14550ae9b..df5a6dda3 100644 --- a/drizzle-seed/src/services/SeedService.ts +++ b/drizzle-seed/src/services/SeedService.ts @@ -66,6 +66,7 @@ export class SeedService { return table1Order - table2Order; }); + const tableNamesSet = new Set(tables.map((table) => table.name)); const tablesPossibleGenerators: Prettify< (typeof tablePossibleGenerators)[] > = tables.map((table) => ({ @@ -211,24 +212,36 @@ export class SeedService { columnPossibleGenerator.isCyclic = true; } - if (foreignKeyColumns[col.name]?.table === undefined && col.notNull === true) { + if ( + (foreignKeyColumns[col.name]?.table === undefined || !tableNamesSet.has(foreignKeyColumns[col.name]!.table)) + && col.notNull === true + ) { throw new Error( `Column '${col.name}' has not null contraint,` + `\nand you didn't specify a table for foreign key on column '${col.name}' in '${table.name}' table.` - + `\n For more details, check this: https://orm.drizzle.team/docs/guides/seeding-with-partially-exposed-tables#example-1`, + + `\n\nFor more details, check this: https://orm.drizzle.team/docs/guides/seeding-with-partially-exposed-tables#example-1`, ); } - const predicate = (cyclicRelation !== undefined || foreignKeyColumns[col.name]?.table === undefined) + const predicate = ( + cyclicRelation !== undefined + || ( + foreignKeyColumns[col.name]?.table === undefined + || !tableNamesSet.has(foreignKeyColumns[col.name]!.table) + ) + ) && col.notNull === false; if (predicate === true) { - if (foreignKeyColumns[col.name]?.table === undefined && col.notNull === false) { + if ( + (foreignKeyColumns[col.name]?.table === undefined + || !tableNamesSet.has(foreignKeyColumns[col.name]!.table)) && col.notNull === false + ) { console.warn( `Column '${col.name}' in '${table.name}' table will be filled with Null values` + `\nbecause you specified neither a table for foreign key on column '${col.name}'` + `\nnor a function for '${col.name}' column in refinements.` - + `\nFor more details, check this: https://orm.drizzle.team/docs/guides/seeding-with-partially-exposed-tables#example-2`, + + `\n\nFor more details, check this: https://orm.drizzle.team/docs/guides/seeding-with-partially-exposed-tables#example-2`, ); } columnPossibleGenerator.generator = new generatorsMap.GenerateDefault[0]({ defaultValue: null }); From a89b04954a6c8085cb7c0c68718a044d7a73e561 Mon Sep 17 00:00:00 2001 From: OleksiiKH0240 Date: Mon, 30 Dec 2024 19:28:34 +0200 Subject: [PATCH 3/4] changes in error messages --- drizzle-seed/src/services/SeedService.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drizzle-seed/src/services/SeedService.ts b/drizzle-seed/src/services/SeedService.ts index df5a6dda3..e68a939e0 100644 --- a/drizzle-seed/src/services/SeedService.ts +++ b/drizzle-seed/src/services/SeedService.ts @@ -111,10 +111,11 @@ export class SeedService { if (!tablesInOutRelations[table.name]?.dependantTableNames.has(fkTableName)) { const reason = tablesInOutRelations[table.name]?.selfRelation === true ? `"${table.name}" table has self reference` - : `"${fkTableName}" table doesn't have reference to "${table.name}" table or` - + `\n you didn't include your one-to-many relation in the seed function schema`; + : `"${fkTableName}" table doesn't have a reference to "${table.name}" table or` + + `\nyou didn't include your one-to-many relation in the seed function schema`; throw new Error( - `${reason}.` + `\nYou can't specify "${fkTableName}" as parameter in ${table.name}.with object.`, + `${reason}.` + `\nYou can't specify "${fkTableName}" as parameter in ${table.name}.with object.` + + `\n\nFor more details, check this: https://orm.drizzle.team/docs/guides/seeding-using-with-option`, ); } From 5724578081e8aeeacd362e2cd7da4d95407c28df Mon Sep 17 00:00:00 2001 From: AndriiSherman Date: Tue, 31 Dec 2024 19:27:23 +0200 Subject: [PATCH 4/4] Bump version --- changelogs/drizzle-seed/0.3.0.md | 40 ++++++++++++++++++++++++++++++++ drizzle-seed/package.json | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 changelogs/drizzle-seed/0.3.0.md diff --git a/changelogs/drizzle-seed/0.3.0.md b/changelogs/drizzle-seed/0.3.0.md new file mode 100644 index 000000000..b2f61882c --- /dev/null +++ b/changelogs/drizzle-seed/0.3.0.md @@ -0,0 +1,40 @@ +# New features + +## Drizzle Relations support + +The `seed` function can now accept Drizzle Relations objects and treat them as foreign key constraints + + +```ts +// schema.ts +import { integer, serial, text, pgTable } from 'drizzle-orm/pg-core'; +import { relations } from 'drizzle-orm'; +export const users = pgTable('users', { + id: serial('id').primaryKey(), + name: text('name').notNull(), +}); +export const usersRelations = relations(users, ({ many }) => ({ + posts: many(posts), +})); +export const posts = pgTable('posts', { + id: serial('id').primaryKey(), + content: text('content').notNull(), + authorId: integer('author_id').notNull(), +}); +export const postsRelations = relations(posts, ({ one }) => ({ + author: one(users, { fields: [posts.authorId], references: [users.id] }), +})); +``` + +```ts +// index.ts +import { seed } from "drizzle-seed"; +import * as schema from './schema.ts' + +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + await seed(db, schema); +} + +main(); +``` \ No newline at end of file diff --git a/drizzle-seed/package.json b/drizzle-seed/package.json index cf983d991..a9287eb28 100644 --- a/drizzle-seed/package.json +++ b/drizzle-seed/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-seed", - "version": "0.2.1", + "version": "0.3.0", "main": "index.js", "type": "module", "scripts": {