diff --git a/changelogs/drizzle-kit/0.29.1.md b/changelogs/drizzle-kit/0.29.1.md new file mode 100644 index 000000000..8e8c6d3d3 --- /dev/null +++ b/changelogs/drizzle-kit/0.29.1.md @@ -0,0 +1 @@ +- Fix SingleStore generate migrations command \ No newline at end of file diff --git a/changelogs/drizzle-kit/0.30.0.md b/changelogs/drizzle-kit/0.30.0.md new file mode 100644 index 000000000..7accf9c9c --- /dev/null +++ b/changelogs/drizzle-kit/0.30.0.md @@ -0,0 +1,7 @@ +Starting from this update, the PostgreSQL dialect will align with the behavior of all other dialects. It will no longer include `IF NOT EXISTS`, `$DO`, or similar statements, which could cause incorrect DDL statements to not fail when an object already exists in the database and should actually fail. + +This change marks our first step toward several major upgrades we are preparing: + +- An updated and improved migration workflow featuring commutative migrations, a revised folder structure, and enhanced collaboration capabilities for migrations. +- Better support for Xata migrations. +- Compatibility with CockroachDB (achieving full compatibility will only require removing serial fields from the migration folder). \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.38.0.md b/changelogs/drizzle-orm/0.38.0.md new file mode 100644 index 000000000..863fe5182 --- /dev/null +++ b/changelogs/drizzle-orm/0.38.0.md @@ -0,0 +1,75 @@ +# Types breaking changes + +A few internal types were changed and extra generic types for length of column types were added in this release. It won't affect anyone, unless you are using those internal types for some custom wrappers, logic, etc. Here is a list of all types that were changed, so if you are relying on those, please review them before upgrading + +- `MySqlCharBuilderInitial` +- `MySqlVarCharBuilderInitial` +- `PgCharBuilderInitial` +- `PgArrayBuilder` +- `PgArray` +- `PgVarcharBuilderInitial` +- `PgBinaryVectorBuilderInitial` +- `PgBinaryVectorBuilder` +- `PgBinaryVector` +- `PgHalfVectorBuilderInitial` +- `PgHalfVectorBuilder` +- `PgHalfVector` +- `PgVectorBuilderInitial` +- `PgVectorBuilder` +- `PgVector` +- `SQLiteTextBuilderInitial` + +# New Features + +- Added new function `getViewSelectedFields` +- Added `$inferSelect` function to views +- Added `InferSelectViewModel` type for views +- Added `isView` function + +# Validator packages updates + +- `drizzle-zod` has been completely rewritten. You can find detailed information about it [here](https://github.com/drizzle-team/drizzle-orm/blob/main/changelogs/drizzle-zod/0.6.0.md) +- `drizzle-valibot` has been completely rewritten. You can find detailed information about it [here](https://github.com/drizzle-team/drizzle-orm/blob/main/changelogs/drizzle-valibot/0.3.0.md) +- `drizzle-typebox` has been completely rewritten. You can find detailed information about it [here](https://github.com/drizzle-team/drizzle-orm/blob/main/changelogs/drizzle-typebox/0.2.0.md) + +Thanks to @L-Mario564 for making more updates than we expected to be shipped in this release. We'll copy his message from a PR regarding improvements made in this release: + +- Output for all packages are now unminified, makes exploring the compiled code easier when published to npm. +- Smaller footprint. Previously, we imported the column types at runtime for each dialect, meaning that for example, if you're just using Postgres then you'd likely only have drizzle-orm and drizzle-orm/pg-core in the build output of your app; however, these packages imported all dialects which could lead to mysql-core and sqlite-core being bundled as well even if they're unused in your app. This is now fixed. +- Slight performance gain. To determine the column data type we used the is function which performs a few checks to ensure the column data type matches. This was slow, as these checks would pile up every quickly when comparing all data types for many fields in a table/view. The easier and faster alternative is to simply go off of the column's columnType property. +- Some changes had to be made at the type level in the ORM package for better compatibility with drizzle-valibot. + +And a set of new features + +- `createSelectSchema` function now also accepts views and enums. +- New function: `createUpdateSchema`, for use in updating queries. +- New function: `createSchemaFactory`, to provide more advanced options and to avoid bloating the parameters of the other schema functions + +# Bug fixes + +- [[FEATURE]: publish packages un-minified](https://github.com/drizzle-team/drizzle-orm/issues/2247) +- [Don't allow unknown keys in drizzle-zod refinement](https://github.com/drizzle-team/drizzle-orm/issues/573) +- [[BUG]:drizzle-zod not working with pgSchema](https://github.com/drizzle-team/drizzle-orm/issues/1458) +- [Add createUpdateSchema to drizzle-zod](https://github.com/drizzle-team/drizzle-orm/issues/503) +- [[BUG]:drizzle-zod produces wrong type](https://github.com/drizzle-team/drizzle-orm/issues/1110) +- [[BUG]:Drizzle-zod:Boolean and Serial types from Schema are defined as enum when using CreateInsertSchema and CreateSelectSchema](https://github.com/drizzle-team/drizzle-orm/issues/1327) +- [[BUG]: Drizzle typebox enum array wrong schema and type](https://github.com/drizzle-team/drizzle-orm/issues/1345) +- [[BUG]:drizzle-zod not working with pgSchema](https://github.com/drizzle-team/drizzle-orm/issues/1458) +- [[BUG]: drizzle-zod not parsing arrays correctly](https://github.com/drizzle-team/drizzle-orm/issues/1609) +- [[BUG]: Drizzle typebox not supporting array](https://github.com/drizzle-team/drizzle-orm/issues/1810) +- [[FEATURE]: Export factory functions from drizzle-zod to allow usage with extended Zod classes](https://github.com/drizzle-team/drizzle-orm/issues/2245) +- [[FEATURE]: Add support for new pipe syntax for drizzle-valibot](https://github.com/drizzle-team/drizzle-orm/issues/2358) +- [[BUG]: drizzle-zod's createInsertSchema() can't handle column of type vector](https://github.com/drizzle-team/drizzle-orm/issues/2424) +- [[BUG]: drizzle-typebox fails to map geometry column to type-box schema](https://github.com/drizzle-team/drizzle-orm/issues/2516) +- [[BUG]: drizzle-valibot does not provide types for returned schemas](https://github.com/drizzle-team/drizzle-orm/issues/2521) +- [[BUG]: Drizzle-typebox types SQLite real field to string](https://github.com/drizzle-team/drizzle-orm/issues/2524) +- [[BUG]: drizzle-zod: documented usage generates type error with exactOptionalPropertyTypes](https://github.com/drizzle-team/drizzle-orm/issues/2550) +- [[BUG]: drizzle-zod does not respect/count db type range](https://github.com/drizzle-team/drizzle-orm/issues/2737) +- [[BUG]: drizzle-zod not overriding optional](https://github.com/drizzle-team/drizzle-orm/issues/2755) +- [[BUG]:drizzle-zod doesn't accept custom id value](https://github.com/drizzle-team/drizzle-orm/issues/2957) +- [[FEATURE]: Support for Database Views in Drizzle Zod](https://github.com/drizzle-team/drizzle-orm/issues/3398) +- [[BUG]: drizzle-valibot return type any](https://github.com/drizzle-team/drizzle-orm/issues/3621) +- [[BUG]: drizzle-zod Type generation results in undefined types](https://github.com/drizzle-team/drizzle-orm/issues/3645) +- [[BUG]: GeneratedAlwaysAs](https://github.com/drizzle-team/drizzle-orm/issues/3511) +- [[FEATURE]: $inferSelect on a view](https://github.com/drizzle-team/drizzle-orm/issues/2610) +- [[BUG]:Can't infer props from view in schema](https://github.com/drizzle-team/drizzle-orm/issues/3392) diff --git a/changelogs/drizzle-seed/0.1.3.md b/changelogs/drizzle-seed/0.1.3.md new file mode 100644 index 000000000..d7bb7ac72 --- /dev/null +++ b/changelogs/drizzle-seed/0.1.3.md @@ -0,0 +1,131 @@ +## Bug fixes + +- https://github.com/drizzle-team/drizzle-orm/issues/3644 +- seeding a table with columns that have .default(sql``) will result in an error + +## Features + +- added support for postgres uuid columns + +Example + +```ts +import { pgTable, uuid } from "drizzle-orm/pg-core"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { seed } from "drizzle-seed"; + +const users = pgTable("users", { + uuid: uuid("uuid"), +}); + +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + // You can let it seed automatically + // await seed(db, { users }); + + // Alternatively, you can manually specify the generator in refine. + await seed(db, { users }, { count: 1000 }).refine((funcs) => ({ + users: { + columns: { + uuid: funcs.uuid(), + }, + }, + })); +} + +main(); +``` + +## + +- added support for postgres array columns + +Example + +```ts +import { pgTable, integer, text, varchar } from "drizzle-orm/pg-core"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { seed } from "drizzle-seed"; + +const users = pgTable("users", { + id: integer().primaryKey(), + name: text().notNull(), + phone_numbers: varchar({ length: 256 }).array(), +}); +``` + +You can specify the `arraySize` parameter in generator options, like `funcs.phoneNumber({ arraySize: 3 })`, to generate 1D arrays. + +```ts +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + await seed(db, { users }, { count: 1000 }).refine((funcs) => ({ + users: { + columns: { + phone_numbers: funcs.phoneNumber({ arraySize: 3 }), + }, + }, + })); +} + +main(); +``` + +Alternatively, you can let it seed automatically, and it will handle arrays of any dimension. + +```ts +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + await seed(db, { users }); +} + +main(); +``` + +## + +- added support for cyclic tables + +You can now seed tables with cyclic relations. + +```ts +import type { AnyPgColumn } from "drizzle-orm/pg-core"; +import { + foreignKey, + integer, + pgTable, + serial, + varchar, +} from "drizzle-orm/pg-core"; + +export const modelTable = pgTable( + "model", + { + id: serial().primaryKey(), + name: varchar().notNull(), + defaultImageId: integer(), + }, + (t) => [ + foreignKey({ + columns: [t.defaultImageId], + foreignColumns: [modelImageTable.id], + }), + ] +); + +export const modelImageTable = pgTable("model_image", { + id: serial().primaryKey(), + url: varchar().notNull(), + caption: varchar(), + modelId: integer() + .notNull() + .references((): AnyPgColumn => modelTable.id), +}); + +async function main() { + const db = drizzle(process.env.DATABASE_URL!); + await seed(db, { modelTable, modelImageTable }); +} + +main(); +``` diff --git a/changelogs/drizzle-typebox/0.2.0.md b/changelogs/drizzle-typebox/0.2.0.md new file mode 100644 index 000000000..aad630f7a --- /dev/null +++ b/changelogs/drizzle-typebox/0.2.0.md @@ -0,0 +1,87 @@ +This version fully updates `drizzle-typebox` integration and makes sure it's compatible with newer typebox versions + +# Breaking Changes + +> You must also have Drizzle ORM v0.38.0 or greater and Typebox v0.34.8 or greater installed. + +- When refining a field, if a schema is provided instead of a callback function, it will ignore the field's nullability and optional status. +- Some data types have more specific schemas for improved validation + +# Improvements + +Thanks to @L-Mario564 for making more updates than we expected to be shipped in this release. We'll copy his message from a PR regarding improvements made in this release: + +- Output for all packages are now unminified, makes exploring the compiled code easier when published to npm. +- Smaller footprint. Previously, we imported the column types at runtime for each dialect, meaning that for example, if you're just using Postgres then you'd likely only have drizzle-orm and drizzle-orm/pg-core in the build output of your app; however, these packages imported all dialects which could lead to mysql-core and sqlite-core being bundled as well even if they're unused in your app. This is now fixed. +- Slight performance gain. To determine the column data type we used the is function which performs a few checks to ensure the column data type matches. This was slow, as these checks would pile up every quickly when comparing all data types for many fields in a table/view. The easier and faster alternative is to simply go off of the column's columnType property. + +# New features + +- `createSelectSchema` function now also accepts views and enums. + +```ts +import { pgEnum } from 'drizzle-orm/pg-core'; +import { createSelectSchema } from 'drizzle-typebox'; +import { Value } from '@sinclair/typebox/value'; + +const roles = pgEnum('roles', ['admin', 'basic']); +const rolesSchema = createSelectSchema(roles); +const parsed: 'admin' | 'basic' = Value.Parse(rolesSchema, ...); + +const usersView = pgView('users_view').as((qb) => qb.select().from(users).where(gt(users.age, 18))); +const usersViewSchema = createSelectSchema(usersView); +const parsed: { id: number; name: string; age: number } = Value.Parse(usersViewSchema, ...); +``` + +- New function: `createUpdateSchema`, for use in updating queries. + +```ts copy +import { pgTable, text, integer } from 'drizzle-orm/pg-core'; +import { createUpdateSchema } from 'drizzle-typebox'; +import { Value } from '@sinclair/typebox/value'; + +const users = pgTable('users', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer().notNull() +}); + +const userUpdateSchema = createUpdateSchema(users); + +const user = { id: 5, name: 'John' }; +const parsed: { name?: string | undefined, age?: number | undefined } = Value.Parse(userUpdateSchema, user); // Error: `id` is a generated column, it can't be updated + +const user = { age: 35 }; +const parsed: { name?: string | undefined, age?: number | undefined } = Value.Parse(userUpdateSchema, user); // Will parse successfully +await db.update(users).set(parsed).where(eq(users.name, 'Jane')); +``` + +- New function: `createSchemaFactory`, to provide more advanced options and to avoid bloating the parameters of the other schema functions + +```ts copy +import { pgTable, text, integer } from 'drizzle-orm/pg-core'; +import { createSchemaFactory } from 'drizzle-typebox'; +import { t } from 'elysia'; // Extended Typebox instance + +const users = pgTable('users', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer().notNull() +}); + +const { createInsertSchema } = createSchemaFactory({ typeboxInstance: t }); + +const userInsertSchema = createInsertSchema(users, { + // We can now use the extended instance + name: (schema) => t.Number({ ...schema }, { error: '`name` must be a string' }) +}); +``` + +- Full support for PG arrays + +```ts +pg.dataType().array(...); + +// Schema +Type.Array(baseDataTypeSchema, { minItems: size, maxItems: size }); +``` \ No newline at end of file diff --git a/changelogs/drizzle-valibot/0.3.0.md b/changelogs/drizzle-valibot/0.3.0.md new file mode 100644 index 000000000..de8ec63b4 --- /dev/null +++ b/changelogs/drizzle-valibot/0.3.0.md @@ -0,0 +1,67 @@ +This version fully updates `drizzle-valibot` integration and makes sure it's compatible with newer valibot versions + +# Breaking Changes + +> You must also have Drizzle ORM v0.38.0 or greater and Valibot v1.0.0-beta.7 or greater installed. + +- When refining a field, if a schema is provided instead of a callback function, it will ignore the field's nullability and optional status. +- Some data types have more specific schemas for improved validation + +# Improvements + +Thanks to @L-Mario564 for making more updates than we expected to be shipped in this release. We'll copy his message from a PR regarding improvements made in this release: + +- Output for all packages are now unminified, makes exploring the compiled code easier when published to npm. +- Smaller footprint. Previously, we imported the column types at runtime for each dialect, meaning that for example, if you're just using Postgres then you'd likely only have drizzle-orm and drizzle-orm/pg-core in the build output of your app; however, these packages imported all dialects which could lead to mysql-core and sqlite-core being bundled as well even if they're unused in your app. This is now fixed. +- Slight performance gain. To determine the column data type we used the is function which performs a few checks to ensure the column data type matches. This was slow, as these checks would pile up every quickly when comparing all data types for many fields in a table/view. The easier and faster alternative is to simply go off of the column's columnType property. +- Some changes had to be made at the type level in the ORM package for better compatibility with drizzle-valibot. + +# New features + +- `createSelectSchema` function now also accepts views and enums. + +```ts copy +import { pgEnum } from 'drizzle-orm/pg-core'; +import { createSelectSchema } from 'drizzle-valibot'; +import { parse } from 'valibot'; + +const roles = pgEnum('roles', ['admin', 'basic']); +const rolesSchema = createSelectSchema(roles); +const parsed: 'admin' | 'basic' = parse(rolesSchema, ...); + +const usersView = pgView('users_view').as((qb) => qb.select().from(users).where(gt(users.age, 18))); +const usersViewSchema = createSelectSchema(usersView); +const parsed: { id: number; name: string; age: number } = parse(usersViewSchema, ...); +``` + +- New function: `createUpdateSchema`, for use in updating queries. + +```ts copy +import { pgTable, text, integer } from 'drizzle-orm/pg-core'; +import { createUpdateSchema } from 'drizzle-valibot'; +import { parse } from 'valibot'; + +const users = pgTable('users', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer().notNull() +}); + +const userUpdateSchema = createUpdateSchema(users); + +const user = { id: 5, name: 'John' }; +const parsed: { name?: string | undefined, age?: number | undefined } = parse(userUpdateSchema, user); // Error: `id` is a generated column, it can't be updated + +const user = { age: 35 }; +const parsed: { name?: string | undefined, age?: number | undefined } = parse(userUpdateSchema, user); // Will parse successfully +await db.update(users).set(parsed).where(eq(users.name, 'Jane')); +``` + +- Full support for PG arrays + +```ts +pg.dataType().array(...); + +// Schema +z.array(baseDataTypeSchema).length(size); +``` \ No newline at end of file diff --git a/changelogs/drizzle-zod/0.6.0.md b/changelogs/drizzle-zod/0.6.0.md new file mode 100644 index 000000000..31ffd4fdb --- /dev/null +++ b/changelogs/drizzle-zod/0.6.0.md @@ -0,0 +1,85 @@ +This version fully updates `drizzle-zod` integration and makes sure it's compatible with newer zod versions + +# Breaking Changes + +> You must also have Drizzle ORM v0.38.0 or greater and Zod v3.0.0 or greater installed. + +- When refining a field, if a schema is provided instead of a callback function, it will ignore the field's nullability and optional status. +- Some data types have more specific schemas for improved validation + +# Improvements + +Thanks to @L-Mario564 for making more updates than we expected to be shipped in this release. We'll copy his message from a PR regarding improvements made in this release: + +- Output for all packages are now unminified, makes exploring the compiled code easier when published to npm. +- Smaller footprint. Previously, we imported the column types at runtime for each dialect, meaning that for example, if you're just using Postgres then you'd likely only have drizzle-orm and drizzle-orm/pg-core in the build output of your app; however, these packages imported all dialects which could lead to mysql-core and sqlite-core being bundled as well even if they're unused in your app. This is now fixed. +- Slight performance gain. To determine the column data type we used the is function which performs a few checks to ensure the column data type matches. This was slow, as these checks would pile up every quickly when comparing all data types for many fields in a table/view. The easier and faster alternative is to simply go off of the column's columnType property. + +# New features + +- `createSelectSchema` function now also accepts views and enums. + +```ts copy +import { pgEnum } from 'drizzle-orm/pg-core'; +import { createSelectSchema } from 'drizzle-zod'; + +const roles = pgEnum('roles', ['admin', 'basic']); +const rolesSchema = createSelectSchema(roles); +const parsed: 'admin' | 'basic' = rolesSchema.parse(...); + +const usersView = pgView('users_view').as((qb) => qb.select().from(users).where(gt(users.age, 18))); +const usersViewSchema = createSelectSchema(usersView); +const parsed: { id: number; name: string; age: number } = usersViewSchema.parse(...); +``` + +- New function: `createUpdateSchema`, for use in updating queries. + +```ts copy +import { pgTable, text, integer } from 'drizzle-orm/pg-core'; +import { createUpdateSchema } from 'drizzle-zod'; + +const users = pgTable('users', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer().notNull() +}); + +const userUpdateSchema = createUpdateSchema(users); + +const user = { id: 5, name: 'John' }; +const parsed: { name?: string | undefined, age?: number | undefined } = userUpdateSchema.parse(user); // Error: `id` is a generated column, it can't be updated + +const user = { age: 35 }; +const parsed: { name?: string | undefined, age?: number | undefined } = userUpdateSchema.parse(user); // Will parse successfully +await db.update(users).set(parsed).where(eq(users.name, 'Jane')); +``` + +- New function: `createSchemaFactory`, to provide more advanced options and to avoid bloating the parameters of the other schema functions + +```ts copy +import { pgTable, text, integer } from 'drizzle-orm/pg-core'; +import { createSchemaFactory } from 'drizzle-zod'; +import { z } from '@hono/zod-openapi'; // Extended Zod instance + +const users = pgTable('users', { + id: integer().generatedAlwaysAsIdentity().primaryKey(), + name: text().notNull(), + age: integer().notNull() +}); + +const { createInsertSchema } = createSchemaFactory({ zodInstance: z }); + +const userInsertSchema = createInsertSchema(users, { + // We can now use the extended instance + name: (schema) => schema.openapi({ example: 'John' }) +}); +``` + +- Full support for PG arrays + +```ts +pg.dataType().array(...); + +// Schema +z.array(baseDataTypeSchema).length(size); +``` \ No newline at end of file diff --git a/drizzle-kit/package.json b/drizzle-kit/package.json index eca469d9b..7d3debd1f 100644 --- a/drizzle-kit/package.json +++ b/drizzle-kit/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-kit", - "version": "0.29.0", + "version": "0.30.0", "homepage": "https://orm.drizzle.team", "keywords": [ "drizzle", diff --git a/drizzle-kit/src/cli/schema.ts b/drizzle-kit/src/cli/schema.ts index 1a0ff93cd..ba30d57ee 100644 --- a/drizzle-kit/src/cli/schema.ts +++ b/drizzle-kit/src/cli/schema.ts @@ -98,7 +98,7 @@ export const generate = command({ } else if (dialect === 'turso') { await prepareAndMigrateLibSQL(opts); } else if (dialect === 'singlestore') { - await prepareAndMigrateSqlite(opts); + await prepareAndMigrateSingleStore(opts); } else { assertUnreachable(dialect); } diff --git a/drizzle-kit/src/introspect-mysql.ts b/drizzle-kit/src/introspect-mysql.ts index ebf30f70d..005a2af42 100644 --- a/drizzle-kit/src/introspect-mysql.ts +++ b/drizzle-kit/src/introspect-mysql.ts @@ -16,9 +16,6 @@ import { import { indexName } from './serializer/mysqlSerializer'; import { unescapeSingleQuotes } from './utils'; -// time precision to fsp -// {mode: "string"} for timestamp by default - const mysqlImportsList = new Set([ 'mysqlTable', 'mysqlEnum', @@ -263,8 +260,7 @@ export const schemaToTypeScript = ( || Object.keys(table.checkConstraint).length > 0 ) { statement += ',\n'; - statement += '(table) => {\n'; - statement += '\treturn {\n'; + statement += '(table) => ['; statement += createTableIndexes( table.name, Object.values(table.indexes), @@ -283,8 +279,7 @@ export const schemaToTypeScript = ( Object.values(table.checkConstraint), withCasing, ); - statement += '\t}\n'; - statement += '}'; + statement += '\n]'; } statement += ');'; @@ -932,7 +927,7 @@ const createTableIndexes = ( const indexGeneratedName = indexName(tableName, it.columns); const escapedIndexName = indexGeneratedName === it.name ? '' : `"${it.name}"`; - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += it.isUnique ? 'uniqueIndex(' : 'index('; statement += `${escapedIndexName})`; statement += `.on(${ @@ -940,7 +935,6 @@ const createTableIndexes = ( .map((it) => `table.${casing(it)}`) .join(', ') }),`; - statement += `\n`; }); return statement; @@ -955,7 +949,7 @@ const createTableUniques = ( unqs.forEach((it) => { const idxKey = casing(it.name); - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += 'unique('; statement += `"${it.name}")`; statement += `.on(${ @@ -963,7 +957,6 @@ const createTableUniques = ( .map((it) => `table.${casing(it)}`) .join(', ') }),`; - statement += `\n`; }); return statement; @@ -976,13 +969,11 @@ const createTableChecks = ( let statement = ''; checks.forEach((it) => { - const checkKey = casing(it.name); - - statement += `\t\t${checkKey}: `; + statement += `\n\t`; statement += 'check('; statement += `"${it.name}", `; statement += `sql\`${it.value.replace(/`/g, '\\`')}\`)`; - statement += `,\n`; + statement += `,`; }); return statement; @@ -997,7 +988,7 @@ const createTablePKs = ( pks.forEach((it) => { let idxKey = casing(it.name); - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += 'primaryKey({ columns: ['; statement += `${ it.columns @@ -1007,7 +998,6 @@ const createTablePKs = ( .join(', ') }]${it.name ? `, name: "${it.name}"` : ''}}`; statement += '),'; - statement += `\n`; }); return statement; @@ -1022,7 +1012,8 @@ const createTableFKs = ( fks.forEach((it) => { const isSelf = it.tableTo === it.tableFrom; const tableTo = isSelf ? 'table' : `${casing(it.tableTo)}`; - statement += `\t\t${casing(it.name)}: foreignKey({\n`; + statement += `\n\t`; + statement += `foreignKey({\n`; statement += `\t\t\tcolumns: [${ it.columnsFrom .map((i) => `table.${casing(i)}`) @@ -1044,7 +1035,7 @@ const createTableFKs = ( ? `.onDelete("${it.onDelete}")` : ''; - statement += `,\n`; + statement += `,`; }); return statement; diff --git a/drizzle-kit/src/introspect-pg.ts b/drizzle-kit/src/introspect-pg.ts index 9c9383ebe..4bb65ee0c 100644 --- a/drizzle-kit/src/introspect-pg.ts +++ b/drizzle-kit/src/introspect-pg.ts @@ -537,8 +537,7 @@ export const schemaToTypeScript = (schema: PgSchemaInternal, casing: Casing) => || Object.keys(table.checkConstraints).length > 0 ) { statement += ', '; - statement += '(table) => {\n'; - statement += '\treturn {\n'; + statement += '(table) => ['; statement += createTableIndexes(table.name, Object.values(table.indexes), casing); statement += createTableFKs(Object.values(table.foreignKeys), schemas, casing); statement += createTablePKs( @@ -558,8 +557,7 @@ export const schemaToTypeScript = (schema: PgSchemaInternal, casing: Casing) => Object.values(table.checkConstraints), casing, ); - statement += '\t}\n'; - statement += '}'; + statement += '\n]'; } statement += ');'; @@ -1216,7 +1214,7 @@ const createTableIndexes = (tableName: string, idxs: Index[], casing: Casing): s ); const escapedIndexName = indexGeneratedName === it.name ? '' : `"${it.name}"`; - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += it.isUnique ? 'uniqueIndex(' : 'index('; statement += `${escapedIndexName})`; statement += `${it.concurrently ? `.concurrently()` : ''}`; @@ -1252,7 +1250,7 @@ const createTableIndexes = (tableName: string, idxs: Index[], casing: Casing): s } statement += it.with && Object.keys(it.with).length > 0 ? `.with(${reverseLogic(it.with)})` : ''; - statement += `,\n`; + statement += `,`; }); return statement; @@ -1262,9 +1260,7 @@ const createTablePKs = (pks: PrimaryKey[], casing: Casing): string => { let statement = ''; pks.forEach((it) => { - let idxKey = withCasing(it.name, casing); - - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += 'primaryKey({ columns: ['; statement += `${ it.columns @@ -1274,7 +1270,7 @@ const createTablePKs = (pks: PrimaryKey[], casing: Casing): string => { .join(', ') }]${it.name ? `, name: "${it.name}"` : ''}}`; statement += ')'; - statement += `,\n`; + statement += `,`; }); return statement; @@ -1297,13 +1293,13 @@ const createTablePolicies = ( return rolesNameToTsKey[v] ? withCasing(rolesNameToTsKey[v], casing) : `"${v}"`; }); - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += 'pgPolicy('; statement += `"${it.name}", { `; statement += `as: "${it.as?.toLowerCase()}", for: "${it.for?.toLowerCase()}", to: [${mappedItTo?.join(', ')}]${ it.using ? `, using: sql\`${it.using}\`` : '' }${it.withCheck ? `, withCheck: sql\`${it.withCheck}\` ` : ''}`; - statement += ` }),\n`; + statement += ` }),`; }); return statement; @@ -1316,14 +1312,12 @@ const createTableUniques = ( let statement = ''; unqs.forEach((it) => { - const idxKey = withCasing(it.name, casing); - - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += 'unique('; statement += `"${it.name}")`; statement += `.on(${it.columns.map((it) => `table.${withCasing(it, casing)}`).join(', ')})`; statement += it.nullsNotDistinct ? `.nullsNotDistinct()` : ''; - statement += `,\n`; + statement += `,`; }); return statement; @@ -1336,12 +1330,11 @@ const createTableChecks = ( let statement = ''; checkConstraints.forEach((it) => { - const checkKey = withCasing(it.name, casing); - statement += `\t\t${checkKey}: `; + statement += `\n\t`; statement += 'check('; statement += `"${it.name}", `; statement += `sql\`${it.value}\`)`; - statement += `,\n`; + statement += `,`; }); return statement; @@ -1356,7 +1349,8 @@ const createTableFKs = (fks: ForeignKey[], schemas: Record, casi const isSelf = it.tableTo === it.tableFrom; const tableTo = isSelf ? 'table' : `${withCasing(paramName, casing)}`; - statement += `\t\t${withCasing(it.name, casing)}: foreignKey({\n`; + statement += `\n\t`; + statement += `foreignKey({\n`; statement += `\t\t\tcolumns: [${it.columnsFrom.map((i) => `table.${withCasing(i, casing)}`).join(', ')}],\n`; statement += `\t\t\tforeignColumns: [${ it.columnsTo.map((i) => `${tableTo}.${withCasing(i, casing)}`).join(', ') @@ -1368,7 +1362,7 @@ const createTableFKs = (fks: ForeignKey[], schemas: Record, casi statement += it.onDelete && it.onDelete !== 'no action' ? `.onDelete("${it.onDelete}")` : ''; - statement += `,\n`; + statement += `,`; }); return statement; diff --git a/drizzle-kit/src/introspect-singlestore.ts b/drizzle-kit/src/introspect-singlestore.ts index 8f93cdfda..09c2feec0 100644 --- a/drizzle-kit/src/introspect-singlestore.ts +++ b/drizzle-kit/src/introspect-singlestore.ts @@ -249,8 +249,7 @@ export const schemaToTypeScript = ( || Object.keys(table.uniqueConstraints).length > 0 ) { statement += ',\n'; - statement += '(table) => {\n'; - statement += '\treturn {\n'; + statement += '(table) => ['; statement += createTableIndexes( table.name, Object.values(table.indexes), @@ -264,8 +263,7 @@ export const schemaToTypeScript = ( Object.values(table.uniqueConstraints), withCasing, ); - statement += '\t}\n'; - statement += '}'; + statement += '\n]'; } statement += ');'; @@ -855,7 +853,7 @@ const createTableIndexes = ( const indexGeneratedName = indexName(tableName, it.columns); const escapedIndexName = indexGeneratedName === it.name ? '' : `"${it.name}"`; - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += it.isUnique ? 'uniqueIndex(' : 'index('; statement += `${escapedIndexName})`; statement += `.on(${ @@ -863,7 +861,6 @@ const createTableIndexes = ( .map((it) => `table.${casing(it)}`) .join(', ') }),`; - statement += `\n`; }); return statement; @@ -876,9 +873,7 @@ const createTableUniques = ( let statement = ''; unqs.forEach((it) => { - const idxKey = casing(it.name); - - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += 'unique('; statement += `"${it.name}")`; statement += `.on(${ @@ -886,7 +881,6 @@ const createTableUniques = ( .map((it) => `table.${casing(it)}`) .join(', ') }),`; - statement += `\n`; }); return statement; @@ -901,7 +895,7 @@ const createTablePKs = ( pks.forEach((it) => { let idxKey = casing(it.name); - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += 'primaryKey({ columns: ['; statement += `${ it.columns @@ -911,7 +905,6 @@ const createTablePKs = ( .join(', ') }]${it.name ? `, name: "${it.name}"` : ''}}`; statement += '),'; - statement += `\n`; }); return statement; diff --git a/drizzle-kit/src/introspect-sqlite.ts b/drizzle-kit/src/introspect-sqlite.ts index 464a32aa3..d3aac6f04 100644 --- a/drizzle-kit/src/introspect-sqlite.ts +++ b/drizzle-kit/src/introspect-sqlite.ts @@ -162,8 +162,7 @@ export const schemaToTypeScript = ( || Object.keys(table.checkConstraints).length > 0 ) { statement += ',\n'; - statement += '(table) => {\n'; - statement += '\treturn {\n'; + statement += '(table) => ['; statement += createTableIndexes( table.name, Object.values(table.indexes), @@ -182,8 +181,7 @@ export const schemaToTypeScript = ( Object.values(table.checkConstraints), casing, ); - statement += '\t}\n'; - statement += '}'; + statement += '\n]'; } statement += ');'; @@ -429,7 +427,7 @@ const createTableIndexes = ( const indexGeneratedName = indexName(tableName, it.columns); const escapedIndexName = indexGeneratedName === it.name ? '' : `"${it.name}"`; - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += it.isUnique ? 'uniqueIndex(' : 'index('; statement += `${escapedIndexName})`; statement += `.on(${ @@ -437,7 +435,6 @@ const createTableIndexes = ( .map((it) => `table.${withCasing(it, casing)}`) .join(', ') }),`; - statement += `\n`; }); return statement; @@ -452,7 +449,7 @@ const createTableUniques = ( unqs.forEach((it) => { const idxKey = withCasing(it.name, casing); - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += 'unique('; statement += `"${it.name}")`; statement += `.on(${ @@ -460,7 +457,6 @@ const createTableUniques = ( .map((it) => `table.${withCasing(it, casing)}`) .join(', ') }),`; - statement += `\n`; }); return statement; @@ -472,13 +468,11 @@ const createTableChecks = ( let statement = ''; checks.forEach((it) => { - const checkKey = withCasing(it.name, casing); - - statement += `\t\t${checkKey}: `; + statement += `\n\t`; statement += 'check('; statement += `"${it.name}", `; statement += `sql\`${it.value}\`)`; - statement += `,\n`; + statement += `,`; }); return statement; @@ -488,7 +482,7 @@ const createTablePKs = (pks: PrimaryKey[], casing: Casing): string => { let statement = ''; pks.forEach((it, i) => { - statement += `\t\tpk${i}: `; + statement += `\n\t`; statement += 'primaryKey({ columns: ['; statement += `${ it.columns @@ -498,7 +492,6 @@ const createTablePKs = (pks: PrimaryKey[], casing: Casing): string => { .join(', ') }]${it.name ? `, name: "${it.name}"` : ''}}`; statement += ')'; - statement += `\n`; }); return statement; @@ -510,7 +503,8 @@ const createTableFKs = (fks: ForeignKey[], casing: Casing): string => { fks.forEach((it) => { const isSelf = it.tableTo === it.tableFrom; const tableTo = isSelf ? 'table' : `${withCasing(it.tableTo, casing)}`; - statement += `\t\t${withCasing(it.name, casing)}: foreignKey(() => ({\n`; + statement += `\n\t`; + statement += `foreignKey(() => ({\n`; statement += `\t\t\tcolumns: [${ it.columnsFrom .map((i) => `table.${withCasing(i, casing)}`) @@ -532,7 +526,7 @@ const createTableFKs = (fks: ForeignKey[], casing: Casing): string => { ? `.onDelete("${it.onDelete}")` : ''; - statement += `,\n`; + statement += `,`; }); return statement; diff --git a/drizzle-orm/package.json b/drizzle-orm/package.json index 3fa8e4bc3..c7528fdbe 100644 --- a/drizzle-orm/package.json +++ b/drizzle-orm/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-orm", - "version": "0.37.0", + "version": "0.38.0", "description": "Drizzle ORM package for SQL databases", "type": "module", "scripts": { diff --git a/drizzle-orm/src/mysql-core/query-builders/update.ts b/drizzle-orm/src/mysql-core/query-builders/update.ts index 9efc4e325..7c6fd40ab 100644 --- a/drizzle-orm/src/mysql-core/query-builders/update.ts +++ b/drizzle-orm/src/mysql-core/query-builders/update.ts @@ -34,7 +34,8 @@ export type MySqlUpdateSetSource = & { [Key in keyof TTable['$inferInsert']]?: | GetColumnData - | SQL; + | SQL + | undefined; } & {}; diff --git a/drizzle-orm/src/mysql-core/table.ts b/drizzle-orm/src/mysql-core/table.ts index e09278dc5..c3d3e581a 100644 --- a/drizzle-orm/src/mysql-core/table.ts +++ b/drizzle-orm/src/mysql-core/table.ts @@ -9,13 +9,16 @@ import type { AnyIndexBuilder } from './indexes.ts'; import type { PrimaryKeyBuilder } from './primary-keys.ts'; import type { UniqueConstraintBuilder } from './unique-constraint.ts'; -export type MySqlTableExtraConfig = Record< - string, +export type MySqlTableExtraConfigValue = | AnyIndexBuilder | CheckBuilder | ForeignKeyBuilder | PrimaryKeyBuilder - | UniqueConstraintBuilder + | UniqueConstraintBuilder; + +export type MySqlTableExtraConfig = Record< + string, + MySqlTableExtraConfigValue >; export type TableConfig = TableConfigBase; @@ -62,7 +65,11 @@ export function mysqlTableWithSchema< >( name: TTableName, columns: TColumnsMap | ((columnTypes: MySqlColumnBuilders) => TColumnsMap), - extraConfig: ((self: BuildColumns) => MySqlTableExtraConfig) | undefined, + extraConfig: + | (( + self: BuildColumns, + ) => MySqlTableExtraConfig | MySqlTableExtraConfigValue[]) + | undefined, schema: TSchemaName, baseName = name, ): MySqlTableWithColumns<{ @@ -109,13 +116,87 @@ export function mysqlTableWithSchema< } export interface MySqlTableFn { + /** + * @deprecated The third parameter of mysqlTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = mysqlTable("users", { + * id: int(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = mysqlTable("users", { + * id: int(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: TColumnsMap, + extraConfig: (self: BuildColumns) => MySqlTableExtraConfig, + ): MySqlTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'mysql'; + }>; + + /** + * @deprecated The third parameter of mysqlTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = mysqlTable("users", { + * id: int(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = mysqlTable("users", { + * id: int(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: (columnTypes: MySqlColumnBuilders) => TColumnsMap, + extraConfig: (self: BuildColumns) => MySqlTableExtraConfig, + ): MySqlTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'mysql'; + }>; + < TTableName extends string, TColumnsMap extends Record, >( name: TTableName, columns: TColumnsMap, - extraConfig?: (self: BuildColumns) => MySqlTableExtraConfig, + extraConfig?: ( + self: BuildColumns, + ) => MySqlTableExtraConfigValue[], ): MySqlTableWithColumns<{ name: TTableName; schema: TSchemaName; @@ -129,7 +210,7 @@ export interface MySqlTableFn( name: TTableName, columns: (columnTypes: MySqlColumnBuilders) => TColumnsMap, - extraConfig?: (self: BuildColumns) => MySqlTableExtraConfig, + extraConfig?: (self: BuildColumns) => MySqlTableExtraConfigValue[], ): MySqlTableWithColumns<{ name: TTableName; schema: TSchemaName; diff --git a/drizzle-orm/src/mysql-core/utils.ts b/drizzle-orm/src/mysql-core/utils.ts index f09f65f3e..2cdc68620 100644 --- a/drizzle-orm/src/mysql-core/utils.ts +++ b/drizzle-orm/src/mysql-core/utils.ts @@ -29,7 +29,8 @@ export function getTableConfig(table: MySqlTable) { if (extraConfigBuilder !== undefined) { const extraConfig = extraConfigBuilder(table[MySqlTable.Symbol.Columns]); - for (const builder of Object.values(extraConfig)) { + const extraValues = Array.isArray(extraConfig) ? extraConfig.flat(1) as any[] : Object.values(extraConfig); + for (const builder of Object.values(extraValues)) { if (is(builder, IndexBuilder)) { indexes.push(builder.build(table)); } else if (is(builder, CheckBuilder)) { diff --git a/drizzle-orm/src/pg-core/query-builders/update.ts b/drizzle-orm/src/pg-core/query-builders/update.ts index c6d04ee35..911916381 100644 --- a/drizzle-orm/src/pg-core/query-builders/update.ts +++ b/drizzle-orm/src/pg-core/query-builders/update.ts @@ -53,7 +53,8 @@ export type PgUpdateSetSource = [Key in keyof TTable['$inferInsert']]?: | GetColumnData | SQL - | PgColumn; + | PgColumn + | undefined; } & {}; diff --git a/drizzle-orm/src/pg-core/table.ts b/drizzle-orm/src/pg-core/table.ts index c3c34c577..7f12e634d 100644 --- a/drizzle-orm/src/pg-core/table.ts +++ b/drizzle-orm/src/pg-core/table.ts @@ -135,7 +135,26 @@ export function pgTableWithSchema< export interface PgTableFn { /** - * @deprecated This overload is deprecated. Use the other method overload instead. + * @deprecated The third parameter of pgTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = pgTable("users", { + * id: integer(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = pgTable("users", { + * id: integer(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` */ < TTableName extends string, @@ -154,7 +173,26 @@ export interface PgTableFn { }>; /** - * @deprecated This overload is deprecated. Use the other method overload instead. + * @deprecated The third parameter of pgTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = pgTable("users", { + * id: integer(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = pgTable("users", { + * id: integer(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` */ < TTableName extends string, diff --git a/drizzle-orm/src/postgres-js/driver.ts b/drizzle-orm/src/postgres-js/driver.ts index b6043f187..69d5a126d 100644 --- a/drizzle-orm/src/postgres-js/driver.ts +++ b/drizzle-orm/src/postgres-js/driver.ts @@ -119,6 +119,11 @@ export namespace drizzle { ): PostgresJsDatabase & { $client: '$client is not available on drizzle.mock()'; } { - return construct({} as any, config) as any; + return construct({ + options: { + parsers: {}, + serializers: {}, + }, + } as any, config) as any; } } diff --git a/drizzle-orm/src/relations.ts b/drizzle-orm/src/relations.ts index ed49c138f..adaee8076 100644 --- a/drizzle-orm/src/relations.ts +++ b/drizzle-orm/src/relations.ts @@ -214,22 +214,27 @@ export type DBQueryConfig< TTableConfig extends TableRelationalConfig = TableRelationalConfig, > = & { - columns?: { - [K in keyof TTableConfig['columns']]?: boolean; - }; - with?: { - [K in keyof TTableConfig['relations']]?: - | true - | DBQueryConfig< - TTableConfig['relations'][K] extends One ? 'one' : 'many', - false, - TSchema, - FindTableByDBName< + columns?: + | { + [K in keyof TTableConfig['columns']]?: boolean; + } + | undefined; + with?: + | { + [K in keyof TTableConfig['relations']]?: + | true + | DBQueryConfig< + TTableConfig['relations'][K] extends One ? 'one' : 'many', + false, TSchema, - TTableConfig['relations'][K]['referencedTableName'] + FindTableByDBName< + TSchema, + TTableConfig['relations'][K]['referencedTableName'] + > > - >; - }; + | undefined; + } + | undefined; extras?: | Record | (( @@ -238,7 +243,8 @@ export type DBQueryConfig< : TTableConfig['columns'] >, operators: { sql: Operators['sql'] }, - ) => Record); + ) => Record) + | undefined; } & (TRelationType extends 'many' ? & { @@ -260,11 +266,12 @@ export type DBQueryConfig< : TTableConfig['columns'] >, operators: OrderByOperators, - ) => ValueOrArray); - limit?: number | Placeholder; + ) => ValueOrArray) + | undefined; + limit?: number | Placeholder | undefined; } & (TIsRoot extends true ? { - offset?: number | Placeholder; + offset?: number | Placeholder | undefined; } : {}) : {}); diff --git a/drizzle-orm/src/singlestore-core/query-builders/update.ts b/drizzle-orm/src/singlestore-core/query-builders/update.ts index 40ca97662..6a843373c 100644 --- a/drizzle-orm/src/singlestore-core/query-builders/update.ts +++ b/drizzle-orm/src/singlestore-core/query-builders/update.ts @@ -34,7 +34,8 @@ export type SingleStoreUpdateSetSource = & { [Key in keyof TTable['$inferInsert']]?: | GetColumnData - | SQL; + | SQL + | undefined; } & {}; diff --git a/drizzle-orm/src/singlestore-core/table.ts b/drizzle-orm/src/singlestore-core/table.ts index 4cc8973ee..ffad22d74 100644 --- a/drizzle-orm/src/singlestore-core/table.ts +++ b/drizzle-orm/src/singlestore-core/table.ts @@ -7,11 +7,14 @@ import type { AnyIndexBuilder } from './indexes.ts'; import type { PrimaryKeyBuilder } from './primary-keys.ts'; import type { UniqueConstraintBuilder } from './unique-constraint.ts'; -export type SingleStoreTableExtraConfig = Record< - string, +export type SingleStoreTableExtraConfigValue = | AnyIndexBuilder | PrimaryKeyBuilder - | UniqueConstraintBuilder + | UniqueConstraintBuilder; + +export type SingleStoreTableExtraConfig = Record< + string, + SingleStoreTableExtraConfigValue >; export type TableConfig = TableConfigBase; @@ -51,7 +54,9 @@ export function singlestoreTableWithSchema< name: TTableName, columns: TColumnsMap | ((columnTypes: SingleStoreColumnBuilders) => TColumnsMap), extraConfig: - | ((self: BuildColumns) => SingleStoreTableExtraConfig) + | (( + self: BuildColumns, + ) => SingleStoreTableExtraConfig | SingleStoreTableExtraConfigValue[]) | undefined, schema: TSchemaName, baseName = name, @@ -98,6 +103,28 @@ export function singlestoreTableWithSchema< } export interface SingleStoreTableFn { + /** + * @deprecated The third parameter of singlestoreTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = singlestoreTable("users", { + * id: int(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = singlestoreTable("users", { + * id: int(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ < TTableName extends string, TColumnsMap extends Record, @@ -112,6 +139,28 @@ export interface SingleStoreTableFn; + /** + * @deprecated The third parameter of singlestoreTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = singlestoreTable("users", { + * id: int(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = singlestoreTable("users", { + * id: int(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ < TTableName extends string, TColumnsMap extends Record, @@ -125,6 +174,36 @@ export interface SingleStoreTableFn; dialect: 'singlestore'; }>; + + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: TColumnsMap, + extraConfig?: ( + self: BuildColumns, + ) => SingleStoreTableExtraConfigValue[], + ): SingleStoreTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'singlestore'; + }>; + + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: (columnTypes: SingleStoreColumnBuilders) => TColumnsMap, + extraConfig?: (self: BuildColumns) => SingleStoreTableExtraConfigValue[], + ): SingleStoreTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'singlestore'; + }>; } export const singlestoreTable: SingleStoreTableFn = (name, columns, extraConfig) => { diff --git a/drizzle-orm/src/singlestore-core/utils.ts b/drizzle-orm/src/singlestore-core/utils.ts index 634b4e261..9ec8b7b0d 100644 --- a/drizzle-orm/src/singlestore-core/utils.ts +++ b/drizzle-orm/src/singlestore-core/utils.ts @@ -22,7 +22,8 @@ export function getTableConfig(table: SingleStoreTable) { if (extraConfigBuilder !== undefined) { const extraConfig = extraConfigBuilder(table[SingleStoreTable.Symbol.Columns]); - for (const builder of Object.values(extraConfig)) { + const extraValues = Array.isArray(extraConfig) ? extraConfig.flat(1) as any[] : Object.values(extraConfig); + for (const builder of Object.values(extraValues)) { if (is(builder, IndexBuilder)) { indexes.push(builder.build(table)); } else if (is(builder, UniqueConstraintBuilder)) { diff --git a/drizzle-orm/src/sqlite-core/query-builders/update.ts b/drizzle-orm/src/sqlite-core/query-builders/update.ts index cc5e4ee30..6915d60a9 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/update.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/update.ts @@ -40,7 +40,8 @@ export type SQLiteUpdateSetSource = [Key in keyof TTable['$inferInsert']]?: | GetColumnData | SQL - | SQLiteColumn; + | SQLiteColumn + | undefined; } & {}; diff --git a/drizzle-orm/src/sqlite-core/table.ts b/drizzle-orm/src/sqlite-core/table.ts index d7c5a060b..5a68f9cb1 100644 --- a/drizzle-orm/src/sqlite-core/table.ts +++ b/drizzle-orm/src/sqlite-core/table.ts @@ -9,13 +9,16 @@ import type { IndexBuilder } from './indexes.ts'; import type { PrimaryKeyBuilder } from './primary-keys.ts'; import type { UniqueConstraintBuilder } from './unique-constraint.ts'; -export type SQLiteTableExtraConfig = Record< - string, +export type SQLiteTableExtraConfigValue = | IndexBuilder | CheckBuilder | ForeignKeyBuilder | PrimaryKeyBuilder - | UniqueConstraintBuilder + | UniqueConstraintBuilder; + +export type SQLiteTableExtraConfig = Record< + string, + SQLiteTableExtraConfigValue >; export type TableConfig = TableConfigBase>; @@ -54,6 +57,28 @@ export type SQLiteTableWithColumns = }; export interface SQLiteTableFn { + /** + * @deprecated The third parameter of sqliteTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = sqliteTable("users", { + * id: int(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = sqliteTable("users", { + * id: int(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ < TTableName extends string, TColumnsMap extends Record, @@ -68,6 +93,28 @@ export interface SQLiteTableFn { dialect: 'sqlite'; }>; + /** + * @deprecated The third parameter of sqliteTable is changing and will only accept an array instead of an object + * + * @example + * Deprecated version: + * ```ts + * export const users = sqliteTable("users", { + * id: int(), + * }, (t) => ({ + * idx: index('custom_name').on(t.id) + * })); + * ``` + * + * New API: + * ```ts + * export const users = sqliteTable("users", { + * id: int(), + * }, (t) => [ + * index('custom_name').on(t.id) + * ]); + * ``` + */ < TTableName extends string, TColumnsMap extends Record, @@ -81,6 +128,36 @@ export interface SQLiteTableFn { columns: BuildColumns; dialect: 'sqlite'; }>; + + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: TColumnsMap, + extraConfig?: ( + self: BuildColumns, + ) => SQLiteTableExtraConfigValue[], + ): SQLiteTableWithColumns<{ + name: TTableName; + schema: TSchema; + columns: BuildColumns; + dialect: 'sqlite'; + }>; + + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: (columnTypes: SQLiteColumnBuilders) => TColumnsMap, + extraConfig?: (self: BuildColumns) => SQLiteTableExtraConfigValue[], + ): SQLiteTableWithColumns<{ + name: TTableName; + schema: TSchema; + columns: BuildColumns; + dialect: 'sqlite'; + }>; } function sqliteTableBase< @@ -90,7 +167,11 @@ function sqliteTableBase< >( name: TTableName, columns: TColumnsMap | ((columnTypes: SQLiteColumnBuilders) => TColumnsMap), - extraConfig?: (self: BuildColumns) => SQLiteTableExtraConfig, + extraConfig: + | (( + self: BuildColumns, + ) => SQLiteTableExtraConfig | SQLiteTableExtraConfigValue[]) + | undefined, schema?: TSchema, baseName = name, ): SQLiteTableWithColumns<{ diff --git a/drizzle-orm/src/sqlite-core/utils.ts b/drizzle-orm/src/sqlite-core/utils.ts index 33ae2c248..7d21483b0 100644 --- a/drizzle-orm/src/sqlite-core/utils.ts +++ b/drizzle-orm/src/sqlite-core/utils.ts @@ -26,7 +26,8 @@ export function getTableConfig(table: TTable) { if (extraConfigBuilder !== undefined) { const extraConfig = extraConfigBuilder(table[SQLiteTable.Symbol.Columns]); - for (const builder of Object.values(extraConfig)) { + const extraValues = Array.isArray(extraConfig) ? extraConfig.flat(1) as any[] : Object.values(extraConfig); + for (const builder of Object.values(extraValues)) { if (is(builder, IndexBuilder)) { indexes.push(builder.build(table)); } else if (is(builder, CheckBuilder)) { diff --git a/drizzle-orm/src/table.ts b/drizzle-orm/src/table.ts index c843fd519..5f6b0d679 100644 --- a/drizzle-orm/src/table.ts +++ b/drizzle-orm/src/table.ts @@ -174,7 +174,7 @@ export type InferModelFromColumns< TColumns[Key], TConfig['override'] > - ]?: GetColumnData; + ]?: GetColumnData | undefined; } : { [ diff --git a/drizzle-orm/type-tests/singlestore/tables.ts b/drizzle-orm/type-tests/singlestore/tables.ts index f7d8e114f..73d9c6993 100644 --- a/drizzle-orm/type-tests/singlestore/tables.ts +++ b/drizzle-orm/type-tests/singlestore/tables.ts @@ -512,7 +512,6 @@ Expect< }); const t = customText('name').notNull(); - Expect< Equal< { diff --git a/drizzle-seed/package.json b/drizzle-seed/package.json index dbac30737..bfa0e39ad 100644 --- a/drizzle-seed/package.json +++ b/drizzle-seed/package.json @@ -1,13 +1,13 @@ { "name": "drizzle-seed", - "version": "0.1.2", + "version": "0.1.3", "main": "index.js", "type": "module", "scripts": { "build": "tsx scripts/build.ts", "pack": "(cd dist && npm pack --pack-destination ..) && rm -f package.tgz && mv *.tgz package.tgz", - "test": "vitest --config ./src/tests/vitest.config.ts", - "test:types": "cd src/type-tests && tsc", + "test": "vitest --config ./vitest.config.ts", + "test:types": "cd type-tests && tsc", "generate-for-tests:pg": "drizzle-kit generate --config=./src/tests/pg/drizzle.config.ts", "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", diff --git a/drizzle-seed/src/index.ts b/drizzle-seed/src/index.ts index 093ecaf5c..8596863fb 100644 --- a/drizzle-seed/src/index.ts +++ b/drizzle-seed/src/index.ts @@ -3,7 +3,7 @@ import { entityKind, getTableName, is, sql } from 'drizzle-orm'; import type { MySqlColumn, MySqlSchema } from 'drizzle-orm/mysql-core'; import { getTableConfig as getMysqlTableConfig, MySqlDatabase, MySqlTable } from 'drizzle-orm/mysql-core'; -import type { PgColumn, PgSchema } from 'drizzle-orm/pg-core'; +import type { PgArray, PgColumn, PgSchema } from 'drizzle-orm/pg-core'; import { getTableConfig as getPgTableConfig, PgDatabase, PgTable } from 'drizzle-orm/pg-core'; import type { SQLiteColumn } from 'drizzle-orm/sqlite-core'; @@ -14,7 +14,7 @@ import { generatorsFuncs } from './services/GeneratorsWrappers.ts'; import seedService from './services/SeedService.ts'; import type { DrizzleStudioObjectType, DrizzleStudioRelationType } from './types/drizzleStudio.ts'; import type { RefinementsType } from './types/seedService.ts'; -import type { Relation, Table } from './types/tables.ts'; +import type { Column, Relation, RelationWithReferences, Table } from './types/tables.ts'; type InferCallbackType< DB extends @@ -226,6 +226,7 @@ export async function seedForDrizzleStudio( hasDefault: col.default === undefined ? false : true, isUnique: col.isUnique === undefined ? false : col.isUnique, notNull: col.notNull, + primary: col.primaryKey, })); tables.push( { @@ -237,6 +238,14 @@ export async function seedForDrizzleStudio( } relations = drizzleStudioRelations.filter((rel) => rel.schema === schemaName && rel.refSchema === schemaName); + const isCyclicRelations = relations.map( + (reli) => { + if (relations.some((relj) => reli.table === relj.refTable && reli.refTable === relj.table)) { + return { ...reli, isCyclic: true }; + } + return { ...reli, isCyclic: false }; + }, + ); refinements = schemasRefinements !== undefined && schemasRefinements[schemaName] !== undefined ? schemasRefinements[schemaName] @@ -245,13 +254,14 @@ export async function seedForDrizzleStudio( const generatedTablesGenerators = seedService.generatePossibleGenerators( sqlDialect, tables, - relations, + isCyclicRelations, + {}, // TODO: fix later refinements, options, ); const generatedTables = await seedService.generateTablesValues( - relations, + isCyclicRelations, generatedTablesGenerators, undefined, undefined, @@ -451,7 +461,7 @@ const resetPostgres = async ( const config = getPgTableConfig(table); config.schema = config.schema === undefined ? 'public' : config.schema; - return `${config.schema}.${config.name}`; + return `"${config.schema}"."${config.name}"`; }); await db.execute(sql.raw(`truncate ${tablesToTruncate.join(',')} cascade;`)); @@ -479,21 +489,37 @@ const seedPostgres = async ( options: { count?: number; seed?: number } = {}, refinements?: RefinementsType, ) => { - const { tables, relations } = getPostgresInfo(schema); + const { tables, relations, tableRelations } = getPostgresInfo(schema); const generatedTablesGenerators = seedService.generatePossibleGenerators( 'postgresql', tables, relations, + tableRelations, refinements, options, ); - await seedService.generateTablesValues( + const preserveCyclicTablesData = relations.some((rel) => rel.isCyclic === true); + + const tablesValues = await seedService.generateTablesValues( relations, generatedTablesGenerators, db, schema, - options, + { ...options, preserveCyclicTablesData }, + ); + + const { filteredTablesGenerators, tablesUniqueNotNullColumn } = seedService.filterCyclicTables( + generatedTablesGenerators, + ); + const updateDataInDb = filteredTablesGenerators.length === 0 ? false : true; + + await seedService.generateTablesValues( + relations, + filteredTablesGenerators, + db, + schema, + { ...options, tablesValues, updateDataInDb, tablesUniqueNotNullColumn }, ); }; @@ -505,10 +531,11 @@ const getPostgresInfo = (schema: { [key: string]: PgTable }) => { ); const tables: Table[] = []; - const relations: Relation[] = []; + const relations: RelationWithReferences[] = []; const dbToTsColumnNamesMapGlobal: { [tableName: string]: { [dbColumnName: string]: string }; } = {}; + const tableRelations: { [tableName: string]: RelationWithReferences[] } = {}; const getDbToTsColumnNamesMap = (table: PgTable) => { let dbToTsColumnNamesMap: { [dbColName: string]: string } = {}; @@ -536,42 +563,80 @@ const getPostgresInfo = (schema: { [key: string]: PgTable }) => { dbToTsColumnNamesMap[col.name] = tsCol; } + // might be empty list + const newRelations = tableConfig.foreignKeys.map((fk) => { + const table = dbToTsTableNamesMap[tableConfig.name] as string; + const refTable = dbToTsTableNamesMap[getTableName(fk.reference().foreignTable)] as string; + + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap( + fk.reference().foreignTable, + ); + + if (tableRelations[refTable] === undefined) { + tableRelations[refTable] = []; + } + return { + table, + columns: fk + .reference() + .columns.map((col) => dbToTsColumnNamesMap[col.name] as string), + refTable, + refColumns: fk + .reference() + .foreignColumns.map( + (fCol) => dbToTsColumnNamesMapForRefTable[fCol.name] as string, + ), + refTableRels: tableRelations[refTable], + }; + }); + relations.push( - ...tableConfig.foreignKeys.map((fk) => { - const table = dbToTsTableNamesMap[tableConfig.name] as string; - const refTable = dbToTsTableNamesMap[getTableName(fk.reference().foreignTable)] as string; - - const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap( - fk.reference().foreignTable, - ); - - return { - table, - columns: fk - .reference() - .columns.map((col) => dbToTsColumnNamesMap[col.name] as string), - refTable, - refColumns: fk - .reference() - .foreignColumns.map( - (fCol) => dbToTsColumnNamesMapForRefTable[fCol.name] as string, - ), - }; - }), + ...newRelations, ); + if (tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] === undefined) { + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] = []; + } + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string]!.push(...newRelations); + + const getAllBaseColumns = ( + baseColumn: PgArray['baseColumn'] & { baseColumn?: PgArray['baseColumn'] }, + ): Column['baseColumn'] => { + const baseColumnResult: Column['baseColumn'] = { + name: baseColumn.name, + columnType: baseColumn.columnType.replace('Pg', '').toLowerCase(), + dataType: baseColumn.dataType, + size: (baseColumn as PgArray).size, + hasDefault: baseColumn.hasDefault, + enumValues: baseColumn.enumValues, + default: baseColumn.default, + isUnique: baseColumn.isUnique, + notNull: baseColumn.notNull, + primary: baseColumn.primary, + baseColumn: baseColumn.baseColumn === undefined ? undefined : getAllBaseColumns(baseColumn.baseColumn), + }; + + return baseColumnResult; + }; + + // console.log(tableConfig.columns); tables.push({ name: dbToTsTableNamesMap[tableConfig.name] as string, columns: tableConfig.columns.map((column) => ({ name: dbToTsColumnNamesMap[column.name] as string, columnType: column.columnType.replace('Pg', '').toLowerCase(), dataType: column.dataType, + size: (column as PgArray).size, hasDefault: column.hasDefault, default: column.default, enumValues: column.enumValues, isUnique: column.isUnique, notNull: column.notNull, + primary: column.primary, generatedIdentityType: column.generatedIdentity?.type, + baseColumn: ((column as PgArray).baseColumn === undefined) + ? undefined + : getAllBaseColumns((column as PgArray).baseColumn), })), primaryKeys: tableConfig.columns .filter((column) => column.primary) @@ -579,7 +644,54 @@ const getPostgresInfo = (schema: { [key: string]: PgTable }) => { }); } - return { tables, relations }; + const isCyclicRelations = relations.map( + (relI) => { + // if (relations.some((relj) => relI.table === relj.refTable && relI.refTable === relj.table)) { + const tableRel = tableRelations[relI.table]!.find((relJ) => relJ.refTable === relI.refTable)!; + if (isRelationCyclic(relI)) { + tableRel['isCyclic'] = true; + return { ...relI, isCyclic: true }; + } + tableRel['isCyclic'] = false; + return { ...relI, isCyclic: false }; + }, + ); + + return { tables, relations: isCyclicRelations, tableRelations }; +}; + +const isRelationCyclic = ( + startRel: RelationWithReferences, +) => { + // self relation + if (startRel.table === startRel.refTable) return false; + + // DFS + const targetTable = startRel.table; + const queue = [startRel]; + let path: string[] = []; + while (queue.length !== 0) { + const currRel = queue.shift(); + + if (path.includes(currRel!.table)) { + const idx = path.indexOf(currRel!.table); + path = path.slice(0, idx); + } + path.push(currRel!.table); + + for (const rel of currRel!.refTableRels) { + // self relation + if (rel.table === rel.refTable) continue; + + if (rel.refTable === targetTable) return true; + + // found cycle, but not the one we are looking for + if (path.includes(rel.refTable)) continue; + queue.unshift(rel); + } + } + + return false; }; // MySql----------------------------------------------------------------------------------------------------- @@ -626,22 +738,38 @@ const seedMySql = async ( options: { count?: number; seed?: number } = {}, refinements?: RefinementsType, ) => { - const { tables, relations } = getMySqlInfo(schema); + const { tables, relations, tableRelations } = getMySqlInfo(schema); const generatedTablesGenerators = seedService.generatePossibleGenerators( 'mysql', tables, relations, + tableRelations, refinements, options, ); - await seedService.generateTablesValues( + const preserveCyclicTablesData = relations.some((rel) => rel.isCyclic === true); + + const tablesValues = await seedService.generateTablesValues( relations, generatedTablesGenerators, db, schema, - options, + { ...options, preserveCyclicTablesData }, + ); + + const { filteredTablesGenerators, tablesUniqueNotNullColumn } = seedService.filterCyclicTables( + generatedTablesGenerators, + ); + const updateDataInDb = filteredTablesGenerators.length === 0 ? false : true; + + await seedService.generateTablesValues( + relations, + filteredTablesGenerators, + db, + schema, + { ...options, tablesValues, updateDataInDb, tablesUniqueNotNullColumn }, ); }; @@ -654,10 +782,11 @@ const getMySqlInfo = (schema: { [key: string]: MySqlTable }) => { ); const tables: Table[] = []; - const relations: Relation[] = []; + const relations: RelationWithReferences[] = []; const dbToTsColumnNamesMapGlobal: { [tableName: string]: { [dbColumnName: string]: string }; } = {}; + const tableRelations: { [tableName: string]: RelationWithReferences[] } = {}; const getDbToTsColumnNamesMap = (table: MySqlTable) => { let dbToTsColumnNamesMap: { [dbColName: string]: string } = {}; @@ -685,29 +814,39 @@ const getMySqlInfo = (schema: { [key: string]: MySqlTable }) => { dbToTsColumnNamesMap[col.name] = tsCol; } + const newRelations = tableConfig.foreignKeys.map((fk) => { + const table = dbToTsTableNamesMap[tableConfig.name] as string; + const refTable = dbToTsTableNamesMap[getTableName(fk.reference().foreignTable)] as string; + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap( + fk.reference().foreignTable, + ); + + if (tableRelations[refTable] === undefined) { + tableRelations[refTable] = []; + } + return { + table, + columns: fk + .reference() + .columns.map((col) => dbToTsColumnNamesMap[col.name] as string), + refTable, + refColumns: fk + .reference() + .foreignColumns.map( + (fCol) => dbToTsColumnNamesMapForRefTable[fCol.name] as string, + ), + refTableRels: tableRelations[refTable], + }; + }); relations.push( - ...tableConfig.foreignKeys.map((fk) => { - const table = dbToTsTableNamesMap[tableConfig.name] as string; - const refTable = dbToTsTableNamesMap[getTableName(fk.reference().foreignTable)] as string; - const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap( - fk.reference().foreignTable, - ); - - return { - table, - columns: fk - .reference() - .columns.map((col) => dbToTsColumnNamesMap[col.name] as string), - refTable, - refColumns: fk - .reference() - .foreignColumns.map( - (fCol) => dbToTsColumnNamesMapForRefTable[fCol.name] as string, - ), - }; - }), + ...newRelations, ); + if (tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] === undefined) { + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] = []; + } + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string]!.push(...newRelations); + tables.push({ name: dbToTsTableNamesMap[tableConfig.name] as string, columns: tableConfig.columns.map((column) => ({ @@ -719,6 +858,7 @@ const getMySqlInfo = (schema: { [key: string]: MySqlTable }) => { enumValues: column.enumValues, isUnique: column.isUnique, notNull: column.notNull, + primary: column.primary, })), primaryKeys: tableConfig.columns .filter((column) => column.primary) @@ -726,7 +866,19 @@ const getMySqlInfo = (schema: { [key: string]: MySqlTable }) => { }); } - return { tables, relations }; + const isCyclicRelations = relations.map( + (relI) => { + const tableRel = tableRelations[relI.table]!.find((relJ) => relJ.refTable === relI.refTable)!; + if (isRelationCyclic(relI)) { + tableRel['isCyclic'] = true; + return { ...relI, isCyclic: true }; + } + tableRel['isCyclic'] = false; + return { ...relI, isCyclic: false }; + }, + ); + + return { tables, relations: isCyclicRelations, tableRelations }; }; // Sqlite------------------------------------------------------------------------------------------------------------------------ @@ -773,22 +925,38 @@ const seedSqlite = async ( options: { count?: number; seed?: number } = {}, refinements?: RefinementsType, ) => { - const { tables, relations } = getSqliteInfo(schema); + const { tables, relations, tableRelations } = getSqliteInfo(schema); const generatedTablesGenerators = seedService.generatePossibleGenerators( 'sqlite', tables, relations, + tableRelations, refinements, options, ); - await seedService.generateTablesValues( + const preserveCyclicTablesData = relations.some((rel) => rel.isCyclic === true); + + const tablesValues = await seedService.generateTablesValues( relations, generatedTablesGenerators, db, schema, - options, + { ...options, preserveCyclicTablesData }, + ); + + const { filteredTablesGenerators, tablesUniqueNotNullColumn } = seedService.filterCyclicTables( + generatedTablesGenerators, + ); + const updateDataInDb = filteredTablesGenerators.length === 0 ? false : true; + + await seedService.generateTablesValues( + relations, + filteredTablesGenerators, + db, + schema, + { ...options, tablesValues, updateDataInDb, tablesUniqueNotNullColumn }, ); }; @@ -800,10 +968,11 @@ const getSqliteInfo = (schema: { [key: string]: SQLiteTable }) => { ); const tables: Table[] = []; - const relations: Relation[] = []; + const relations: RelationWithReferences[] = []; const dbToTsColumnNamesMapGlobal: { [tableName: string]: { [dbColumnName: string]: string }; } = {}; + const tableRelations: { [tableName: string]: RelationWithReferences[] } = {}; const getDbToTsColumnNamesMap = (table: SQLiteTable) => { let dbToTsColumnNamesMap: { [dbColName: string]: string } = {}; @@ -831,29 +1000,40 @@ const getSqliteInfo = (schema: { [key: string]: SQLiteTable }) => { dbToTsColumnNamesMap[col.name] = tsCol; } + const newRelations = tableConfig.foreignKeys.map((fk) => { + const table = dbToTsTableNamesMap[tableConfig.name] as string; + const refTable = dbToTsTableNamesMap[getTableName(fk.reference().foreignTable)] as string; + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap( + fk.reference().foreignTable, + ); + + if (tableRelations[refTable] === undefined) { + tableRelations[refTable] = []; + } + return { + table, + columns: fk + .reference() + .columns.map((col) => dbToTsColumnNamesMap[col.name] as string), + refTable, + refColumns: fk + .reference() + .foreignColumns.map( + (fCol) => dbToTsColumnNamesMapForRefTable[fCol.name] as string, + ), + refTableRels: tableRelations[refTable], + }; + }); + relations.push( - ...tableConfig.foreignKeys.map((fk) => { - const table = dbToTsTableNamesMap[tableConfig.name] as string; - const refTable = dbToTsTableNamesMap[getTableName(fk.reference().foreignTable)] as string; - const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap( - fk.reference().foreignTable, - ); - - return { - table, - columns: fk - .reference() - .columns.map((col) => dbToTsColumnNamesMap[col.name] as string), - refTable, - refColumns: fk - .reference() - .foreignColumns.map( - (fCol) => dbToTsColumnNamesMapForRefTable[fCol.name] as string, - ), - }; - }), + ...newRelations, ); + if (tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] === undefined) { + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] = []; + } + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string]!.push(...newRelations); + tables.push({ name: dbToTsTableNamesMap[tableConfig.name] as string, columns: tableConfig.columns.map((column) => ({ @@ -865,6 +1045,7 @@ const getSqliteInfo = (schema: { [key: string]: SQLiteTable }) => { enumValues: column.enumValues, isUnique: column.isUnique, notNull: column.notNull, + primary: column.primary, })), primaryKeys: tableConfig.columns .filter((column) => column.primary) @@ -872,8 +1053,22 @@ const getSqliteInfo = (schema: { [key: string]: SQLiteTable }) => { }); } - return { tables, relations }; + const isCyclicRelations = relations.map( + (relI) => { + const tableRel = tableRelations[relI.table]!.find((relJ) => relJ.refTable === relI.refTable)!; + if (isRelationCyclic(relI)) { + tableRel['isCyclic'] = true; + return { ...relI, isCyclic: true }; + } + tableRel['isCyclic'] = false; + return { ...relI, isCyclic: false }; + }, + ); + + return { tables, relations: isCyclicRelations, tableRelations }; }; +export { default as cities } from './datasets/cityNames.ts'; +export { default as countries } from './datasets/countries.ts'; export { default as firstNames } from './datasets/firstNames.ts'; export { default as lastNames } from './datasets/lastNames.ts'; diff --git a/drizzle-seed/src/services/GeneratorsWrappers.ts b/drizzle-seed/src/services/GeneratorsWrappers.ts index ae2d1d47d..06d6adeb5 100644 --- a/drizzle-seed/src/services/GeneratorsWrappers.ts +++ b/drizzle-seed/src/services/GeneratorsWrappers.ts @@ -12,7 +12,7 @@ import loremIpsumSentences from '../datasets/loremIpsumSentences.ts'; import phonesInfo from '../datasets/phonesInfo.ts'; import states from '../datasets/states.ts'; import streetSuffix from '../datasets/streetSuffix.ts'; -import { fastCartesianProduct, fillTemplate, getWeightedIndices } from './utils.ts'; +import { fastCartesianProduct, fillTemplate, getWeightedIndices, isObject } from './utils.ts'; export abstract class AbstractGenerator { static readonly [entityKind]: string = 'AbstractGenerator'; @@ -22,12 +22,71 @@ export abstract class AbstractGenerator { public uniqueVersionOfGen?: new(params: T) => AbstractGenerator; public dataType?: string; public timeSpent?: number; + public arraySize?: number; + public baseColumnDataType?: string; constructor(public params: T) {} - abstract init(params: { count: number | { weight: number; count: number | number[] }[]; seed: number }): void; + init(params: { count: number | { weight: number; count: number | number[] }[]; seed: number }): void; + init() { + if ((this.params as any).arraySize !== undefined) { + this.arraySize = (this.params as any).arraySize; + } + + if ((this.params as any).isUnique !== undefined) { + if ((this.params as any).isUnique === false && this.isUnique === true) { + throw new Error('specifying non unique generator to unique column.'); + } + + this.isUnique = (this.params as any).isUnique; + } + } abstract generate(params: { i: number }): number | string | boolean | unknown | undefined | void; + + getEntityKind(): string { + const constructor = this.constructor as typeof AbstractGenerator; + return constructor[entityKind]; + } + + replaceIfUnique({ count, seed }: { count: number; seed: number }) { + if ( + this.uniqueVersionOfGen !== undefined + && this.isUnique === true + ) { + const uniqueGen = new this.uniqueVersionOfGen({ + ...this.params, + }); + uniqueGen.init({ + count, + seed, + }); + uniqueGen.isUnique = this.isUnique; + uniqueGen.dataType = this.dataType; + + return uniqueGen; + } + return; + } + + replaceIfArray({ count, seed }: { count: number; seed: number }) { + if (!(this.getEntityKind() === 'GenerateArray') && this.arraySize !== undefined) { + const uniqueGen = this.replaceIfUnique({ count, seed }); + const baseColumnGen = uniqueGen === undefined ? this : uniqueGen; + baseColumnGen.dataType = this.baseColumnDataType; + const arrayGen = new GenerateArray( + { + baseColumnGen, + size: this.arraySize, + }, + ); + arrayGen.init({ count, seed }); + + return arrayGen; + } + + return; + } } function createGenerator, T>( @@ -44,6 +103,26 @@ function createGenerator, T>( } // Generators Classes ----------------------------------------------------------------------------------------------------------------------- +export class GenerateArray extends AbstractGenerator<{ baseColumnGen: AbstractGenerator; size?: number }> { + static override readonly [entityKind]: string = 'GenerateArray'; + public override arraySize = 10; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + this.arraySize = this.params.size === undefined ? this.arraySize : this.params.size; + this.params.baseColumnGen.init({ count: count * this.arraySize, seed }); + } + + generate() { + const array = []; + for (let i = 0; i < this.arraySize; i++) { + array.push(this.params.baseColumnGen.generate({ i })); + } + + return array; + } +} + export class GenerateWeightedCount extends AbstractGenerator<{}> { static override readonly [entityKind]: string = 'GenerateWeightedCount'; @@ -53,7 +132,7 @@ export class GenerateWeightedCount extends AbstractGenerator<{}> { weightedCount: { weight: number; count: number | number[] }[]; } | undefined; - init({ seed, count }: { count: { weight: number; count: number | number[] }[]; seed: number }) { + override init({ seed, count }: { count: { weight: number; count: number | number[] }[]; seed: number }) { const rng = prand.xoroshiro128plus(seed); const weightedIndices = getWeightedIndices(count.map((val) => val.weight)); this.state = { rng, weightedIndices, weightedCount: count }; @@ -85,18 +164,17 @@ export class GenerateWeightedCount extends AbstractGenerator<{}> { export class HollowGenerator extends AbstractGenerator<{}> { static override readonly [entityKind]: string = 'HollowGenerator'; - init() {} + override init() {} generate() {} } export class GenerateDefault extends AbstractGenerator<{ defaultValue: unknown; + arraySize?: number; }> { static override readonly [entityKind]: string = 'GenerateDefault'; - init() {} - generate() { return this.params.defaultValue; } @@ -108,6 +186,7 @@ export class GenerateValuesFromArray extends AbstractGenerator< | (number | string | boolean | undefined)[] | { weight: number; values: (number | string | boolean | undefined)[] }[]; isUnique?: boolean; + arraySize?: number; } > { static override readonly [entityKind]: string = 'GenerateValuesFromArray'; @@ -134,7 +213,7 @@ export class GenerateValuesFromArray extends AbstractGenerator< } if ( - typeof values[0] === 'object' + isObject(values[0]) && !(values as { weight: number; values: any[] }[]).every((val) => val.values.length !== 0) ) { throw new Error('One of weighted values length equals zero.'); @@ -155,7 +234,7 @@ export class GenerateValuesFromArray extends AbstractGenerator< } let allValuesCount = values.length; - if (typeof values[0] === 'object') { + if (isObject(values[0])) { allValuesCount = (values as { values: any[] }[]).reduce((acc, currVal) => acc + currVal.values.length, 0); } @@ -163,9 +242,9 @@ export class GenerateValuesFromArray extends AbstractGenerator< notNull === true && maxRepeatedValuesCount !== undefined && ( - (typeof values[0] !== 'object' && typeof maxRepeatedValuesCount === 'number' + (!isObject(values[0]) && typeof maxRepeatedValuesCount === 'number' && maxRepeatedValuesCount * values.length < count) - || (typeof values[0] === 'object' && typeof maxRepeatedValuesCount === 'number' + || (isObject(values[0]) && typeof maxRepeatedValuesCount === 'number' && maxRepeatedValuesCount * allValuesCount < count) ) ) { @@ -188,8 +267,8 @@ export class GenerateValuesFromArray extends AbstractGenerator< if ( isUnique === true && notNull === true && ( - (typeof values[0] !== 'object' && values.length < count) - || (typeof values[0] === 'object' && allValuesCount < count) + (!isObject(values[0]) && values.length < count) + || (isObject(values[0]) && allValuesCount < count) ) ) { // console.log(maxRepeatedValuesCount, values.length, allValuesCount, count) @@ -197,14 +276,9 @@ export class GenerateValuesFromArray extends AbstractGenerator< } } - init({ count, seed }: { count: number; seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('Specifying non unique generator to unique column.'); - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); - this.isUnique = this.params.isUnique; - } this.checks({ count }); let { maxRepeatedValuesCount } = this; @@ -213,7 +287,7 @@ export class GenerateValuesFromArray extends AbstractGenerator< const values = params.values; let valuesWeightedIndices; - if (typeof values[0] === 'object') { + if (isObject(values[0])) { valuesWeightedIndices = getWeightedIndices((values as { weight: number }[]).map((val) => val.weight)); if (isUnique === true && notNull === true) { let idx: number, valueIdx: number, rng = prand.xoroshiro128plus(seed); @@ -259,12 +333,12 @@ export class GenerateValuesFromArray extends AbstractGenerator< let genIndicesObjList: GenerateUniqueInt[] | undefined; if (maxRepeatedValuesCount !== undefined) { - if (typeof values[0] !== 'object') { + if (!isObject(values[0])) { genIndicesObj = new GenerateUniqueInt({ minValue: 0, maxValue: values.length - 1 }); genIndicesObj.genMaxRepeatedValuesCount = genMaxRepeatedValuesCount; genIndicesObj.skipCheck = true; genIndicesObj.init({ count, seed }); - } else if (typeof values[0] === 'object') { + } else if (isObject(values[0])) { genIndicesObjList = []; for (const obj of values as { weight: number; values: (number | string | boolean | undefined)[] }[]) { const genIndicesObj = new GenerateUniqueInt({ minValue: 0, maxValue: obj.values.length - 1 }); @@ -334,7 +408,7 @@ export class GenerateSelfRelationsValuesFromArray extends AbstractGenerator<{ va firstValues: (string | number | boolean)[]; } | undefined; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { let rng = prand.xoroshiro128plus(seed); // generate 15-40 % values with the same value as reference column @@ -368,7 +442,7 @@ export class GenerateIntPrimaryKey extends AbstractGenerator<{}> { public maxValue?: number | bigint; - init({ count }: { count: number; seed: number }) { + override init({ count }: { count: number; seed: number }) { if (this.maxValue !== undefined && count > this.maxValue) { throw new Error('count exceeds max number for this column type.'); } @@ -389,7 +463,8 @@ export class GenerateNumber extends AbstractGenerator< maxValue?: number; precision?: number; isUnique?: boolean; - } | undefined + arraySize?: number; + } > { static override readonly [entityKind]: string = 'GenerateNumber'; @@ -401,16 +476,8 @@ export class GenerateNumber extends AbstractGenerator< } | undefined; override uniqueVersionOfGen = GenerateUniqueNumber; - init({ seed }: { seed: number }) { - if (this.params === undefined) this.params = {}; - - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { seed: number; count: number }) { + super.init({ count, seed }); let { minValue, maxValue, precision } = this.params; if (precision === undefined) { @@ -452,7 +519,7 @@ export class GenerateUniqueNumber extends AbstractGenerator< maxValue?: number; precision?: number; isUnique?: boolean; - } | undefined + } > { static override readonly [entityKind]: string = 'GenerateUniqueNumber'; @@ -464,8 +531,7 @@ export class GenerateUniqueNumber extends AbstractGenerator< } | undefined; public override isUnique = true; - init({ count, seed }: { count: number; seed: number }) { - if (this.params === undefined) this.params = {}; + override init({ count, seed }: { count: number; seed: number }) { let { minValue, maxValue, precision } = this.params; if (precision === undefined) { @@ -505,6 +571,7 @@ export class GenerateInt extends AbstractGenerator<{ minValue?: number | bigint; maxValue?: number | bigint; isUnique?: boolean; + arraySize?: number; }> { static override readonly [entityKind]: string = 'GenerateInt'; @@ -515,14 +582,8 @@ export class GenerateInt extends AbstractGenerator<{ } | undefined; override uniqueVersionOfGen = GenerateUniqueInt; - init({ seed }: { count: number; seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); let { minValue, maxValue } = this.params; @@ -594,7 +655,7 @@ export class GenerateUniqueInt extends AbstractGenerator<{ public override isUnique = true; public override timeSpent = 0; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { const rng = prand.xoroshiro128plus(seed); let { minValue, maxValue } = this.params; @@ -747,14 +808,16 @@ export class GenerateUniqueInt extends AbstractGenerator<{ } } -export class GenerateBoolean extends AbstractGenerator<{}> { +export class GenerateBoolean extends AbstractGenerator<{ arraySize?: number }> { static override readonly [entityKind]: string = 'GenerateBoolean'; private state: { rng: prand.RandomGenerator; } | undefined; - init({ seed }: { seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + const rng = prand.xoroshiro128plus(seed); this.state = { rng }; @@ -772,7 +835,11 @@ export class GenerateBoolean extends AbstractGenerator<{}> { } } -export class GenerateDate extends AbstractGenerator<{ minDate?: string | Date; maxDate?: string | Date }> { +export class GenerateDate extends AbstractGenerator<{ + minDate?: string | Date; + maxDate?: string | Date; + arraySize?: number; +}> { static override readonly [entityKind]: string = 'GenerateDate'; private state: { @@ -781,10 +848,12 @@ export class GenerateDate extends AbstractGenerator<{ minDate?: string | Date; m maxDate: Date; } | undefined; - init({ seed }: { seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); const rng = prand.xoroshiro128plus(seed); let { minDate, maxDate } = this.params; + const anchorDate = new Date('2024-05-08'); const deltaMilliseconds = 4 * 31536000000; @@ -832,14 +901,16 @@ export class GenerateDate extends AbstractGenerator<{ minDate?: string | Date; m return date; } } -export class GenerateTime extends AbstractGenerator<{}> { +export class GenerateTime extends AbstractGenerator<{ arraySize?: number }> { static override readonly [entityKind]: string = 'GenerateTime'; private state: { rng: prand.RandomGenerator; } | undefined; - init({ seed }: { seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + const rng = prand.xoroshiro128plus(seed); this.state = { rng }; @@ -873,10 +944,10 @@ export class GenerateTimestampInt extends AbstractGenerator<{ unitOfTime?: 'seco generateTimestampObj: GenerateTimestamp; } | undefined; - init({ seed }: { seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { const generateTimestampObj = new GenerateTimestamp({}); generateTimestampObj.dataType = 'date'; - generateTimestampObj.init({ seed }); + generateTimestampObj.init({ count, seed }); this.state = { generateTimestampObj }; } @@ -899,14 +970,16 @@ export class GenerateTimestampInt extends AbstractGenerator<{ unitOfTime?: 'seco } } -export class GenerateTimestamp extends AbstractGenerator<{}> { +export class GenerateTimestamp extends AbstractGenerator<{ arraySize?: number }> { static override readonly [entityKind]: string = 'GenerateTimestamp'; private state: { rng: prand.RandomGenerator; } | undefined; - init({ seed }: { seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + const rng = prand.xoroshiro128plus(seed); this.state = { rng }; @@ -941,14 +1014,16 @@ export class GenerateTimestamp extends AbstractGenerator<{}> { } } -export class GenerateDatetime extends AbstractGenerator<{}> { +export class GenerateDatetime extends AbstractGenerator<{ arraySize?: number }> { static override readonly [entityKind]: string = 'GenerateDatetime'; private state: { rng: prand.RandomGenerator; } | undefined; - init({ seed }: { seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + const rng = prand.xoroshiro128plus(seed); this.state = { rng }; @@ -983,14 +1058,16 @@ export class GenerateDatetime extends AbstractGenerator<{}> { } } -export class GenerateYear extends AbstractGenerator<{}> { +export class GenerateYear extends AbstractGenerator<{ arraySize?: number }> { static override readonly [entityKind]: string = 'GenerateYear'; private state: { rng: prand.RandomGenerator; } | undefined; - init({ seed }: { seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + const rng = prand.xoroshiro128plus(seed); this.state = { rng }; @@ -1016,7 +1093,7 @@ export class GenerateYear extends AbstractGenerator<{}> { } } -export class GenerateJson extends AbstractGenerator<{}> { +export class GenerateJson extends AbstractGenerator<{ arraySize?: number }> { static override readonly [entityKind]: string = 'GenerateJson'; private state: { @@ -1029,15 +1106,18 @@ export class GenerateJson extends AbstractGenerator<{}> { seed: number; } | undefined; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + const emailGeneratorObj = new GenerateEmail({}); emailGeneratorObj.init({ count, seed }); const nameGeneratorObj = new GenerateFirstName({}); - nameGeneratorObj.init({ seed }); + nameGeneratorObj.init({ count, seed }); const booleanGeneratorObj = new GenerateBoolean({}); booleanGeneratorObj.init({ + count, seed, }); @@ -1050,7 +1130,7 @@ export class GenerateJson extends AbstractGenerator<{}> { const dateGeneratorObj = new GenerateDate({}); dateGeneratorObj.dataType = 'string'; - dateGeneratorObj.init({ seed }); + dateGeneratorObj.init({ count, seed }); const visitedCountriesNumberGeneratorObj = new GenerateInt({ minValue: 0, maxValue: 4 }); visitedCountriesNumberGeneratorObj.init( @@ -1124,7 +1204,7 @@ export class GenerateEnum extends AbstractGenerator<{ enumValues: (string | numb enumValuesGenerator: GenerateValuesFromArray; } | undefined; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { const { enumValues } = this.params; const enumValuesGenerator = new GenerateValuesFromArray({ values: enumValues }); enumValuesGenerator.init({ count, seed }); @@ -1140,20 +1220,17 @@ export class GenerateEnum extends AbstractGenerator<{ enumValues: (string | numb } } -export class GenerateInterval extends AbstractGenerator<{ isUnique?: boolean }> { +export class GenerateInterval extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { static override readonly [entityKind]: string = 'GenerateInterval'; private state: { rng: prand.RandomGenerator } | undefined; override uniqueVersionOfGen = GenerateUniqueInterval; - init({ seed }: { count: number; seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); const rng = prand.xoroshiro128plus(seed); this.state = { rng }; @@ -1202,8 +1279,9 @@ export class GenerateUniqueInterval extends AbstractGenerator<{ isUnique?: boole rng: prand.RandomGenerator; intervalSet: Set; } | undefined; + public override isUnique = true; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { const maxUniqueIntervalsNumber = 6 * 13 * 29 * 25 * 61 * 61; if (count > maxUniqueIntervalsNumber) { throw new RangeError(`count exceeds max number of unique intervals(${maxUniqueIntervalsNumber})`); @@ -1253,20 +1331,17 @@ export class GenerateUniqueInterval extends AbstractGenerator<{ isUnique?: boole } } -export class GenerateString extends AbstractGenerator<{ isUnique?: boolean }> { +export class GenerateString extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { static override readonly [entityKind]: string = 'GenerateString'; private state: { rng: prand.RandomGenerator } | undefined; override uniqueVersionOfGen = GenerateUniqueString; - init({ seed }: { seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); const rng = prand.xoroshiro128plus(seed); this.state = { rng }; @@ -1308,7 +1383,7 @@ export class GenerateUniqueString extends AbstractGenerator<{ isUnique?: boolean private state: { rng: prand.RandomGenerator } | undefined; public override isUnique = true; - init({ seed }: { seed: number }) { + override init({ seed }: { seed: number }) { const rng = prand.xoroshiro128plus(seed); this.state = { rng }; } @@ -1345,8 +1420,55 @@ export class GenerateUniqueString extends AbstractGenerator<{ isUnique?: boolean } } +export class GenerateUUID extends AbstractGenerator<{ + arraySize?: number; +}> { + static override readonly [entityKind]: string = 'GenerateUUID'; + + public override isUnique = true; + + private state: { rng: prand.RandomGenerator } | undefined; + + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + + const rng = prand.xoroshiro128plus(seed); + this.state = { rng }; + } + + generate() { + if (this.state === undefined) { + throw new Error('state is not defined.'); + } + // TODO generate uuid using string generator + const stringChars = '1234567890abcdef'; + let idx: number, + currStr: string; + const strLength = 36; + + // uuid v4 + const uuidTemplate = '########-####-4###-####-############'; + currStr = ''; + for (let i = 0; i < strLength; i++) { + [idx, this.state.rng] = prand.uniformIntDistribution( + 0, + stringChars.length - 1, + this.state.rng, + ); + + if (uuidTemplate[i] === '#') { + currStr += stringChars[idx]; + continue; + } + currStr += uuidTemplate[i]; + } + return currStr; + } +} + export class GenerateFirstName extends AbstractGenerator<{ isUnique?: boolean; + arraySize?: number; }> { static override readonly [entityKind]: string = 'GenerateFirstName'; @@ -1356,14 +1478,8 @@ export class GenerateFirstName extends AbstractGenerator<{ } | undefined; override uniqueVersionOfGen = GenerateUniqueFirstName; - init({ seed }: { seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); const rng = prand.xoroshiro128plus(seed); @@ -1395,7 +1511,7 @@ export class GenerateUniqueFirstName extends AbstractGenerator<{ } | undefined; public override isUnique = true; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { if (count > firstNames.length) { throw new Error('count exceeds max number of unique first names.'); } @@ -1418,7 +1534,10 @@ export class GenerateUniqueFirstName extends AbstractGenerator<{ } } -export class GenerateLastName extends AbstractGenerator<{ isUnique?: boolean }> { +export class GenerateLastName extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { static override readonly [entityKind]: string = 'GenerateLastName'; private state: { @@ -1426,14 +1545,8 @@ export class GenerateLastName extends AbstractGenerator<{ isUnique?: boolean }> } | undefined; override uniqueVersionOfGen = GenerateUniqueLastName; - init({ seed }: { seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); const rng = prand.xoroshiro128plus(seed); @@ -1459,7 +1572,7 @@ export class GenerateUniqueLastName extends AbstractGenerator<{ isUnique?: boole } | undefined; public override isUnique = true; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { if (count > lastNames.length) { throw new Error('count exceeds max number of unique last names.'); } @@ -1484,6 +1597,7 @@ export class GenerateUniqueLastName extends AbstractGenerator<{ isUnique?: boole export class GenerateFullName extends AbstractGenerator<{ isUnique?: boolean; + arraySize?: number; }> { static override readonly [entityKind]: string = 'GenerateFullName'; @@ -1492,14 +1606,8 @@ export class GenerateFullName extends AbstractGenerator<{ } | undefined; override uniqueVersionOfGen = GenerateUniqueFullName; - init({ seed }: { seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); const rng = prand.xoroshiro128plus(seed); @@ -1537,7 +1645,7 @@ export class GenerateUniqueFullName extends AbstractGenerator<{ public override isUnique = true; public override timeSpent = 0; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { const t0 = new Date(); const maxUniqueFullNamesNumber = firstNames.length * lastNames.length; @@ -1581,7 +1689,9 @@ export class GenerateUniqueFullName extends AbstractGenerator<{ } } -export class GenerateEmail extends AbstractGenerator<{}> { +export class GenerateEmail extends AbstractGenerator<{ + arraySize?: number; +}> { static override readonly [entityKind]: string = 'GenerateEmail'; private state: { @@ -1591,7 +1701,9 @@ export class GenerateEmail extends AbstractGenerator<{}> { public override timeSpent: number = 0; public override isUnique = true; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + const domainsArray = emailDomains; const adjectivesArray = adjectives; const namesArray = firstNames; @@ -1638,6 +1750,7 @@ export class GeneratePhoneNumber extends AbstractGenerator<{ template?: string; prefixes?: string[]; generatedDigitsNumbers?: number | number[]; + arraySize?: number; }> { static override readonly [entityKind]: string = 'GeneratePhoneNumber'; @@ -1651,7 +1764,9 @@ export class GeneratePhoneNumber extends AbstractGenerator<{ } | undefined; public override isUnique = true; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + let { generatedDigitsNumbers } = this.params; const { prefixes, template } = this.params; @@ -1809,7 +1924,10 @@ export class GeneratePhoneNumber extends AbstractGenerator<{ } } -export class GenerateCountry extends AbstractGenerator<{ isUnique?: boolean }> { +export class GenerateCountry extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { static override readonly [entityKind]: string = 'GenerateCountry'; private state: { @@ -1817,14 +1935,8 @@ export class GenerateCountry extends AbstractGenerator<{ isUnique?: boolean }> { } | undefined; override uniqueVersionOfGen = GenerateUniqueCountry; - init({ seed }: { count: number; seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); const rng = prand.xoroshiro128plus(seed); @@ -1853,7 +1965,7 @@ export class GenerateUniqueCountry extends AbstractGenerator<{ isUnique?: boolea } | undefined; public override isUnique = true; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { if (count > countries.length) { throw new Error('count exceeds max number of unique countries.'); } @@ -1876,14 +1988,18 @@ export class GenerateUniqueCountry extends AbstractGenerator<{ isUnique?: boolea } } -export class GenerateJobTitle extends AbstractGenerator<{}> { +export class GenerateJobTitle extends AbstractGenerator<{ + arraySize?: number; +}> { static override readonly [entityKind]: string = 'GenerateJobTitle'; private state: { rng: prand.RandomGenerator; } | undefined; - init({ seed }: { seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + const rng = prand.xoroshiro128plus(seed); this.state = { rng }; @@ -1901,7 +2017,10 @@ export class GenerateJobTitle extends AbstractGenerator<{}> { } } -export class GenerateStreetAdddress extends AbstractGenerator<{ isUnique?: boolean }> { +export class GenerateStreetAdddress extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { static override readonly [entityKind]: string = 'GenerateStreetAdddress'; private state: { @@ -1910,14 +2029,8 @@ export class GenerateStreetAdddress extends AbstractGenerator<{ isUnique?: boole } | undefined; override uniqueVersionOfGen = GenerateUniqueStreetAdddress; - init({ seed }: { count: number; seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); const rng = prand.xoroshiro128plus(seed); const possStreetNames = [firstNames, lastNames]; @@ -1958,8 +2071,9 @@ export class GenerateUniqueStreetAdddress extends AbstractGenerator<{ isUnique?: arraysToChooseFrom: string[][]; }[]; } | undefined; + public override isUnique = true; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { const streetNumberStrs = Array.from({ length: 999 }, (_, i) => String(i + 1)); const maxUniqueStreetnamesNumber = streetNumberStrs.length * firstNames.length * streetSuffix.length + streetNumberStrs.length * firstNames.length * streetSuffix.length; @@ -2031,7 +2145,10 @@ export class GenerateUniqueStreetAdddress extends AbstractGenerator<{ isUnique?: } } -export class GenerateCity extends AbstractGenerator<{ isUnique?: boolean }> { +export class GenerateCity extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { static override readonly [entityKind]: string = 'GenerateCity'; private state: { @@ -2039,14 +2156,8 @@ export class GenerateCity extends AbstractGenerator<{ isUnique?: boolean }> { } | undefined; override uniqueVersionOfGen = GenerateUniqueCity; - init({ seed }: { seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); const rng = prand.xoroshiro128plus(seed); @@ -2073,7 +2184,7 @@ export class GenerateUniqueCity extends AbstractGenerator<{ isUnique?: boolean } } | undefined; public override isUnique = true; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { if (count > cityNames.length) { throw new Error('count exceeds max number of unique cities.'); } @@ -2096,7 +2207,10 @@ export class GenerateUniqueCity extends AbstractGenerator<{ isUnique?: boolean } } } -export class GeneratePostcode extends AbstractGenerator<{ isUnique?: boolean }> { +export class GeneratePostcode extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { static override readonly [entityKind]: string = 'GeneratePostcode'; private state: { @@ -2105,14 +2219,8 @@ export class GeneratePostcode extends AbstractGenerator<{ isUnique?: boolean }> } | undefined; override uniqueVersionOfGen = GenerateUniquePostcode; - init({ seed }: { seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); const rng = prand.xoroshiro128plus(seed); const templates = ['#####', '#####-####']; @@ -2162,8 +2270,9 @@ export class GenerateUniquePostcode extends AbstractGenerator<{ isUnique?: boole maxUniquePostcodeNumber: number; }[]; } | undefined; + public override isUnique = true; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { const maxUniquePostcodeNumber = Math.pow(10, 5) + Math.pow(10, 9); if (count > maxUniquePostcodeNumber) { throw new RangeError( @@ -2226,14 +2335,18 @@ export class GenerateUniquePostcode extends AbstractGenerator<{ isUnique?: boole } } -export class GenerateState extends AbstractGenerator<{}> { +export class GenerateState extends AbstractGenerator<{ + arraySize?: number; +}> { static override readonly [entityKind]: string = 'GenerateState'; private state: { rng: prand.RandomGenerator; } | undefined; - init({ seed }: { seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + const rng = prand.xoroshiro128plus(seed); this.state = { rng }; @@ -2251,7 +2364,10 @@ export class GenerateState extends AbstractGenerator<{}> { } } -export class GenerateCompanyName extends AbstractGenerator<{ isUnique?: boolean }> { +export class GenerateCompanyName extends AbstractGenerator<{ + isUnique?: boolean; + arraySize?: number; +}> { static override readonly [entityKind]: string = 'GenerateCompanyName'; private state: { @@ -2260,14 +2376,8 @@ export class GenerateCompanyName extends AbstractGenerator<{ isUnique?: boolean } | undefined; override uniqueVersionOfGen = GenerateUniqueCompanyName; - init({ seed }: { seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); const rng = prand.xoroshiro128plus(seed); const templates = [ @@ -2329,16 +2439,9 @@ export class GenerateUniqueCompanyName extends AbstractGenerator<{ isUnique?: bo arraysToChooseFrom: string[][]; }[]; } | undefined; + public override isUnique = true; - init({ count, seed }: { count: number; seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } - + override init({ count, seed }: { count: number; seed: number }) { const maxUniqueCompanyNameNumber = lastNames.length * companyNameSuffixes.length + Math.pow(lastNames.length, 2) + Math.pow(lastNames.length, 2) + Math.pow(lastNames.length, 3); if (count > maxUniqueCompanyNameNumber) { @@ -2419,14 +2522,19 @@ export class GenerateUniqueCompanyName extends AbstractGenerator<{ isUnique?: bo } } -export class GenerateLoremIpsum extends AbstractGenerator<{ sentencesCount?: number }> { +export class GenerateLoremIpsum extends AbstractGenerator<{ + sentencesCount?: number; + arraySize?: number; +}> { static override readonly [entityKind]: string = 'GenerateLoremIpsum'; private state: { rng: prand.RandomGenerator; } | undefined; - init({ seed }: { seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); + const rng = prand.xoroshiro128plus(seed); if (this.params.sentencesCount === undefined) this.params.sentencesCount = 1; @@ -2456,7 +2564,7 @@ export class WeightedRandomGenerator extends AbstractGenerator<{ weight: number; weightedIndices: number[]; } | undefined; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { const weights = this.params.map((weightedGen) => weightedGen.weight); const weightedIndices = getWeightedIndices(weights); @@ -2517,6 +2625,7 @@ export class GeneratePoint extends AbstractGenerator<{ maxXValue?: number; minYValue?: number; maxYValue?: number; + arraySize?: number; }> { static override readonly [entityKind]: string = 'GeneratePoint'; @@ -2526,28 +2635,22 @@ export class GeneratePoint extends AbstractGenerator<{ } | undefined; override uniqueVersionOfGen = GenerateUniquePoint; - init({ seed }: { count: number; seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); const xCoordinateGen = new GenerateNumber({ minValue: this.params.minXValue, maxValue: this.params.maxXValue, precision: 10, }); - xCoordinateGen.init({ seed }); + xCoordinateGen.init({ count, seed }); const yCoordinateGen = new GenerateNumber({ minValue: this.params.minYValue, maxValue: this.params.maxYValue, precision: 10, }); - yCoordinateGen.init({ seed }); + yCoordinateGen.init({ count, seed }); this.state = { xCoordinateGen, yCoordinateGen }; } @@ -2584,8 +2687,9 @@ export class GenerateUniquePoint extends AbstractGenerator<{ xCoordinateGen: GenerateUniqueNumber; yCoordinateGen: GenerateUniqueNumber; } | undefined; + public override isUnique = true; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { const xCoordinateGen = new GenerateUniqueNumber({ minValue: this.params.minXValue, maxValue: this.params.maxXValue, @@ -2630,6 +2734,7 @@ export class GenerateLine extends AbstractGenerator<{ maxBValue?: number; minCValue?: number; maxCValue?: number; + arraySize?: number; }> { static override readonly [entityKind]: string = 'GenerateLine'; @@ -2640,35 +2745,29 @@ export class GenerateLine extends AbstractGenerator<{ } | undefined; override uniqueVersionOfGen = GenerateUniqueLine; - init({ seed }: { count: number; seed: number }) { - if (this.params.isUnique !== undefined) { - if (this.params.isUnique === false && this.isUnique === true) { - throw new Error('specifying non unique generator to unique column.'); - } - - this.isUnique = this.params.isUnique; - } + override init({ count, seed }: { count: number; seed: number }) { + super.init({ count, seed }); const aCoefficientGen = new GenerateNumber({ minValue: this.params.minAValue, maxValue: this.params.maxAValue, precision: 10, }); - aCoefficientGen.init({ seed }); + aCoefficientGen.init({ count, seed }); const bCoefficientGen = new GenerateNumber({ minValue: this.params.minBValue, maxValue: this.params.maxBValue, precision: 10, }); - bCoefficientGen.init({ seed }); + bCoefficientGen.init({ count, seed }); const cCoefficientGen = new GenerateNumber({ minValue: this.params.minCValue, maxValue: this.params.maxCValue, precision: 10, }); - cCoefficientGen.init({ seed }); + cCoefficientGen.init({ count, seed }); this.state = { aCoefficientGen, bCoefficientGen, cCoefficientGen }; } @@ -2715,8 +2814,9 @@ export class GenerateUniqueLine extends AbstractGenerator<{ bCoefficientGen: GenerateUniqueNumber; cCoefficientGen: GenerateUniqueNumber; } | undefined; + public override isUnique = true; - init({ count, seed }: { count: number; seed: number }) { + override init({ count, seed }: { count: number; seed: number }) { const aCoefficientGen = new GenerateUniqueNumber({ minValue: this.params.minAValue, maxValue: this.params.maxAValue, @@ -2771,6 +2871,7 @@ export const generatorsFuncs = { /** * generates same given value each time the generator is called. * @param defaultValue - value you want to generate + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -2789,6 +2890,7 @@ export const generatorsFuncs = { * generates values from given array * @param values - array of values you want to generate. can be array of weighted values. * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -2850,6 +2952,7 @@ export const generatorsFuncs = { * precision equals 10 means that values will be accurate to one tenth (1.2, 34.6); * precision equals 100 means that values will be accurate to one hundredth (1.23, 34.67). * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -2871,6 +2974,7 @@ export const generatorsFuncs = { * @param minValue - lower border of range. * @param maxValue - upper border of range. * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -2889,6 +2993,7 @@ export const generatorsFuncs = { /** * generates boolean values(true or false) + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -2908,6 +3013,7 @@ export const generatorsFuncs = { * generates date within given range. * @param minDate - lower border of range. * @param maxDate - upper border of range. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -2925,6 +3031,8 @@ export const generatorsFuncs = { /** * generates time in 24 hours style. + * @param arraySize - number of elements in each one-dimensional array. + * * @example * ```ts * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ @@ -2941,6 +3049,8 @@ export const generatorsFuncs = { /** * generates timestamps. + * @param arraySize - number of elements in each one-dimensional array. + * * @example * ```ts * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ @@ -2957,6 +3067,8 @@ export const generatorsFuncs = { /** * generates datetime objects. + * @param arraySize - number of elements in each one-dimensional array. + * * @example * ```ts * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ @@ -2973,6 +3085,8 @@ export const generatorsFuncs = { /** * generates years. + * @param arraySize - number of elements in each one-dimensional array. + * * @example * ```ts * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ @@ -2989,6 +3103,7 @@ export const generatorsFuncs = { /** * generates json objects with fixed structure. + * @param arraySize - number of elements in each one-dimensional array. * * json structure can equal this: * ``` @@ -3033,6 +3148,7 @@ export const generatorsFuncs = { * interval example: "1 years 12 days 5 minutes" * * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. * @example * ```ts * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ @@ -3050,6 +3166,7 @@ export const generatorsFuncs = { /** * generates random strings. * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3065,9 +3182,30 @@ export const generatorsFuncs = { string: createGenerator(GenerateString), // uniqueString: createGenerator(GenerateUniqueString), + /** + * generates v4 UUID strings if arraySize is not specified, or v4 UUID 1D arrays if it is. + * + * @param arraySize - number of elements in each one-dimensional array. + * + * @example + * ```ts + * await seed(db, schema, { count: 1000 }).refine((funcs) => ({ + * users: { + * columns: { + * uuid: funcs.uuid({ + * arraySize: 4 + * }) + * }, + * }, + * })); + * ``` + */ + uuid: createGenerator(GenerateUUID), + /** * generates person's first names. * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3086,6 +3224,7 @@ export const generatorsFuncs = { /** * generates person's last names. * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3104,6 +3243,7 @@ export const generatorsFuncs = { /** * generates person's full names. * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3121,6 +3261,7 @@ export const generatorsFuncs = { /** * generates unique emails. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3137,6 +3278,7 @@ export const generatorsFuncs = { /** * generates unique phone numbers. + * @param arraySize - number of elements in each one-dimensional array. * * @param template - phone number template, where all '#' symbols will be substituted with generated digits. * @param prefixes - array of any string you want to be your phone number prefixes.(not compatible with template property) @@ -3177,6 +3319,7 @@ export const generatorsFuncs = { /** * generates country's names. * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3195,6 +3338,7 @@ export const generatorsFuncs = { /** * generates city's names. * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3213,6 +3357,7 @@ export const generatorsFuncs = { /** * generates street address. * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3230,6 +3375,7 @@ export const generatorsFuncs = { /** * generates job titles. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3248,6 +3394,7 @@ export const generatorsFuncs = { * generates postal codes. * * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3265,6 +3412,7 @@ export const generatorsFuncs = { /** * generates states of America. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3283,6 +3431,7 @@ export const generatorsFuncs = { * generates company's names. * * @param isUnique - property that controls if generated values gonna be unique or not. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3302,6 +3451,7 @@ export const generatorsFuncs = { * generates 'lorem ipsum' text sentences. * * @param sentencesCount - number of sentences you want to generate as one generated value(string). + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3324,6 +3474,7 @@ export const generatorsFuncs = { * @param maxXValue - upper bound of range for x coordinate. * @param minYValue - lower bound of range for y coordinate. * @param maxYValue - upper bound of range for y coordinate. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts @@ -3357,6 +3508,7 @@ export const generatorsFuncs = { * @param maxBValue - upper bound of range for y parameter. * @param minCValue - lower bound of range for y parameter. * @param maxCValue - upper bound of range for y parameter. + * @param arraySize - number of elements in each one-dimensional array. * * @example * ```ts diff --git a/drizzle-seed/src/services/SeedService.ts b/drizzle-seed/src/services/SeedService.ts index 2588f110e..4ee43a921 100644 --- a/drizzle-seed/src/services/SeedService.ts +++ b/drizzle-seed/src/services/SeedService.ts @@ -1,9 +1,9 @@ -import { entityKind, is } from 'drizzle-orm'; -import type { MySqlTable } from 'drizzle-orm/mysql-core'; +import { entityKind, eq, is } from 'drizzle-orm'; +import type { MySqlTable, MySqlTableWithColumns } from 'drizzle-orm/mysql-core'; import { MySqlDatabase } from 'drizzle-orm/mysql-core'; -import type { PgTable } from 'drizzle-orm/pg-core'; +import type { PgTable, PgTableWithColumns } from 'drizzle-orm/pg-core'; import { PgDatabase } from 'drizzle-orm/pg-core'; -import type { SQLiteTable } from 'drizzle-orm/sqlite-core'; +import type { SQLiteTable, SQLiteTableWithColumns } from 'drizzle-orm/sqlite-core'; import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core'; import type { GeneratePossibleGeneratorsColumnType, @@ -11,9 +11,10 @@ import type { RefinementsType, TableGeneratorsType, } from '../types/seedService.ts'; -import type { Column, Prettify, Relation, Table } from '../types/tables.ts'; +import type { Column, Prettify, Relation, RelationWithReferences, Table } from '../types/tables.ts'; import type { AbstractGenerator } from './GeneratorsWrappers.ts'; import { + GenerateArray, GenerateBoolean, GenerateDate, GenerateDatetime, @@ -33,12 +34,13 @@ import { GenerateTime, GenerateTimestamp, GenerateUniqueString, + GenerateUUID, GenerateValuesFromArray, GenerateWeightedCount, GenerateYear, HollowGenerator, } from './GeneratorsWrappers.ts'; -import { generateHashFromString } from './utils.ts'; +import { equalSets, generateHashFromString } from './utils.ts'; class SeedService { static readonly [entityKind]: string = 'SeedService'; @@ -54,7 +56,8 @@ class SeedService { generatePossibleGenerators = ( connectionType: 'postgresql' | 'mysql' | 'sqlite', tables: Table[], - relations: Relation[], + relations: (Relation & { isCyclic: boolean })[], + tableRelations: { [tableName: string]: RelationWithReferences[] }, refinements?: RefinementsType, options?: { count?: number; seed?: number }, ) => { @@ -64,9 +67,16 @@ class SeedService { // sorting table in order which they will be filled up (tables with foreign keys case) // relations = relations.filter(rel => rel.type === "one"); - const tablesInOutRelations = this.getTablesInOutRelations(relations); + const { tablesInOutRelations } = this.getInfoFromRelations(relations); const orderedTablesNames = this.getOrderedTablesList(tablesInOutRelations); tables = tables.sort((table1, table2) => { + const rel = relations.find((rel) => rel.table === table1.name && rel.refTable === table2.name); + + if (rel?.isCyclic === true) { + const reverseRel = relations.find((rel) => rel.table === table2.name && rel.refTable === table1.name); + return this.cyclicTablesCompare(table1, table2, rel, reverseRel); + } + const table1Order = orderedTablesNames.indexOf( table1.name, ), @@ -178,8 +188,12 @@ class SeedService { columnName: col.name, isUnique: col.isUnique, notNull: col.notNull, + primary: col.primary, generatedIdentityType: col.generatedIdentityType, generator: undefined, + isCyclic: false, + wasDefinedBefore: false, + wasRefined: false, }; if ( @@ -189,39 +203,56 @@ class SeedService { && refinements[table.name]!.columns[col.name] !== undefined ) { const genObj = refinements[table.name]!.columns[col.name]!; - // for now only GenerateValuesFromArray support notNull property + // TODO: for now only GenerateValuesFromArray support notNull property genObj.notNull = col.notNull; + if (col.dataType === 'array') { + if (col.baseColumn?.dataType === 'array' && col.baseColumn?.columnType === 'array') { + throw new Error("for now you can't specify generators for columns of dimensition greater than 1."); + } + + genObj.baseColumnDataType = col.baseColumn?.dataType; + } columnPossibleGenerator.generator = genObj; + columnPossibleGenerator.wasRefined = true; } else if (Object.hasOwn(foreignKeyColumns, col.name)) { // TODO: I might need to assign repeatedValuesCount to column there instead of doing so in generateTablesValues + const cyclicRelation = tableRelations[table.name]!.find((rel) => + rel.isCyclic === true + && rel.columns.includes(col.name) + ); + + if (cyclicRelation !== undefined) { + columnPossibleGenerator.isCyclic = true; + } + const predicate = cyclicRelation !== undefined && col.notNull === false; + + if (predicate === true) { + columnPossibleGenerator.generator = new GenerateDefault({ defaultValue: null }); + columnPossibleGenerator.wasDefinedBefore = true; + } + columnPossibleGenerator.generator = new HollowGenerator({}); - } else if (col.hasDefault && col.default !== undefined) { - columnPossibleGenerator.generator = new GenerateDefault({ - defaultValue: col.default, - }); } // TODO: rewrite pickGeneratorFor... using new col properties: isUnique and notNull else if (connectionType === 'postgresql') { - columnPossibleGenerator = this.pickGeneratorForPostgresColumn( - columnPossibleGenerator, + columnPossibleGenerator.generator = this.pickGeneratorForPostgresColumn( table, col, ); } else if (connectionType === 'mysql') { - columnPossibleGenerator = this.pickGeneratorForMysqlColumn( - columnPossibleGenerator, + columnPossibleGenerator.generator = this.pickGeneratorForMysqlColumn( table, col, ); } else if (connectionType === 'sqlite') { - columnPossibleGenerator = this.pickGeneratorForSqlite( - columnPossibleGenerator, + columnPossibleGenerator.generator = this.pickGeneratorForSqlite( table, col, ); } if (columnPossibleGenerator.generator === undefined) { + console.log(col); throw new Error( `column with type ${col.columnType} is not supported for now.`, ); @@ -229,6 +260,7 @@ class SeedService { columnPossibleGenerator.generator.isUnique = col.isUnique; columnPossibleGenerator.generator.dataType = col.dataType; + tablePossibleGenerators.columnsPossibleGenerators.push( columnPossibleGenerator, ); @@ -238,7 +270,45 @@ class SeedService { return tablesPossibleGenerators; }; - getOrderedTablesList = (tablesInOutRelations: ReturnType): string[] => { + cyclicTablesCompare = ( + table1: Table, + table2: Table, + relation: Relation & { isCyclic: boolean }, + reverseRelation: Relation & { isCyclic: boolean } | undefined, + ) => { + // TODO: revise + const hasTable1NotNullColumns = relation.columns.some((colIName) => + table1.columns.find((colJ) => colJ.name === colIName)?.notNull === true + ); + + if (reverseRelation !== undefined) { + const hasTable2NotNullColumns = reverseRelation.columns.some((colIName) => + table2.columns.find((colJ) => colJ.name === colIName)?.notNull === true + ); + + if (hasTable1NotNullColumns && hasTable2NotNullColumns) { + throw new Error( + `The '${table1.name}' and '${table2.name}' tables have not null foreign keys. You can't seed cyclic tables with not null foreign key columns.`, + ); + } + + if (hasTable1NotNullColumns) return 1; + else if (hasTable2NotNullColumns) return -1; + return 0; + } + + if (hasTable1NotNullColumns) { + return 1; + } + return 0; + + // if (hasTable1NotNullColumns) return 1; + // else if (hasTable2NotNullColumns) return -1; + }; + + getOrderedTablesList = ( + tablesInOutRelations: ReturnType['tablesInOutRelations'], + ): string[] => { const leafTablesNames = Object.entries(tablesInOutRelations) .filter( (tableRel) => @@ -266,7 +336,13 @@ class SeedService { tablesInOutRelations[parent]!.requiredTableNames.delete(orderedTableName); } - if (tablesInOutRelations[parent]!.requiredTableNames.size === 0) { + if ( + tablesInOutRelations[parent]!.requiredTableNames.size === 0 + || equalSets( + tablesInOutRelations[parent]!.requiredTableNames, + tablesInOutRelations[parent]!.dependantTableNames, + ) + ) { orderedTablesNames.push(parent); } else { leafTablesNames.push(parent); @@ -279,7 +355,7 @@ class SeedService { return orderedTablesNames; }; - getTablesInOutRelations = (relations: Relation[]) => { + getInfoFromRelations = (relations: (Relation & { isCyclic: boolean })[]) => { const tablesInOutRelations: { [tableName: string]: { out: number; @@ -291,7 +367,13 @@ class SeedService { }; } = {}; + // const cyclicRelations: { [cyclicTableName: string]: Relation & { isCyclic: boolean } } = {}; + for (const rel of relations) { + // if (rel.isCyclic) { + // cyclicRelations[rel.table] = rel; + // } + if (tablesInOutRelations[rel.table] === undefined) { tablesInOutRelations[rel.table] = { out: 0, @@ -326,7 +408,7 @@ class SeedService { } } - return tablesInOutRelations; + return { tablesInOutRelations }; }; getWeightedWithCount = ( @@ -345,11 +427,13 @@ class SeedService { }; // TODO: revise serial part generators + pickGeneratorForPostgresColumn = ( - columnPossibleGenerator: Prettify, table: Table, col: Column, ) => { + let generator: AbstractGenerator | undefined; + // INT ------------------------------------------------------------------------------------------------------------ if ( (col.columnType.includes('serial') @@ -358,8 +442,11 @@ class SeedService { || col.columnType.includes('bigint')) && table.primaryKeys.includes(col.name) ) { - columnPossibleGenerator.generator = new GenerateIntPrimaryKey({}); - return columnPossibleGenerator; + generator = new GenerateIntPrimaryKey({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } let minValue: number | bigint | undefined; @@ -404,17 +491,23 @@ class SeedService { && !col.columnType.includes('interval') && !col.columnType.includes('point') ) { - columnPossibleGenerator.generator = new GenerateInt({ + generator = new GenerateInt({ minValue, maxValue, }); - return columnPossibleGenerator; + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } if (col.columnType.includes('serial')) { - const genObj = new GenerateIntPrimaryKey({}); - genObj.maxValue = maxValue; - columnPossibleGenerator.generator = genObj; + generator = new GenerateIntPrimaryKey({}); + + (generator as GenerateIntPrimaryKey).maxValue = maxValue; + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } // NUMBER(real, double, decimal, numeric) @@ -424,8 +517,11 @@ class SeedService { || col.columnType === 'decimal' || col.columnType === 'numeric' ) { - columnPossibleGenerator.generator = new GenerateNumber({}); - return columnPossibleGenerator; + generator = new GenerateNumber({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } // STRING @@ -435,8 +531,11 @@ class SeedService { || col.columnType === 'char') && table.primaryKeys.includes(col.name) ) { - columnPossibleGenerator.generator = new GenerateUniqueString({}); - return columnPossibleGenerator; + generator = new GenerateUniqueString({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } if ( @@ -445,8 +544,11 @@ class SeedService { || col.columnType === 'char') && col.name.toLowerCase().includes('name') ) { - columnPossibleGenerator.generator = new GenerateFirstName({}); - return columnPossibleGenerator; + generator = new GenerateFirstName({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } if ( @@ -455,8 +557,11 @@ class SeedService { || col.columnType === 'char') && col.name.toLowerCase().includes('email') ) { - columnPossibleGenerator.generator = new GenerateEmail({}); - return columnPossibleGenerator; + generator = new GenerateEmail({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } if ( @@ -465,73 +570,131 @@ class SeedService { || col.columnType === 'char' ) { // console.log(col, table) - columnPossibleGenerator.generator = new GenerateString({}); - return columnPossibleGenerator; + generator = new GenerateString({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; + } + + // UUID + if (col.columnType === 'uuid') { + generator = new GenerateUUID({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } // BOOLEAN if (col.columnType === 'boolean') { - columnPossibleGenerator.generator = new GenerateBoolean({}); - return columnPossibleGenerator; + generator = new GenerateBoolean({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } // DATE, TIME, TIMESTAMP if (col.columnType.includes('date')) { - columnPossibleGenerator.generator = new GenerateDate({}); - return columnPossibleGenerator; + generator = new GenerateDate({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } if (col.columnType === 'time') { - columnPossibleGenerator.generator = new GenerateTime({}); - return columnPossibleGenerator; + generator = new GenerateTime({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } if (col.columnType.includes('timestamp')) { - columnPossibleGenerator.generator = new GenerateTimestamp({}); - return columnPossibleGenerator; + generator = new GenerateTimestamp({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } // JSON, JSONB if (col.columnType === 'json' || col.columnType === 'jsonb') { - columnPossibleGenerator.generator = new GenerateJson({}); - return columnPossibleGenerator; + generator = new GenerateJson({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } // if (col.columnType === "jsonb") { - // columnPossibleGenerator.generator = new GenerateJsonb({}); - // return columnPossibleGenerator; + // const generator = new GenerateJsonb({}); + // return generator; // } // ENUM if (col.enumValues !== undefined) { - columnPossibleGenerator.generator = new GenerateEnum({ + generator = new GenerateEnum({ enumValues: col.enumValues, }); - return columnPossibleGenerator; + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } // INTERVAL if (col.columnType === 'interval') { - columnPossibleGenerator.generator = new GenerateInterval({}); - return columnPossibleGenerator; + generator = new GenerateInterval({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } // POINT, LINE if (col.columnType.includes('point')) { - columnPossibleGenerator.generator = new GeneratePoint({}); - return columnPossibleGenerator; + generator = new GeneratePoint({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } if (col.columnType.includes('line')) { - columnPossibleGenerator.generator = new GenerateLine({}); - return columnPossibleGenerator; + generator = new GenerateLine({}); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; + } + + // ARRAY + if (col.columnType.includes('array') && col.baseColumn !== undefined) { + const baseColumnGen = this.pickGeneratorForPostgresColumn(table, col.baseColumn!) as AbstractGenerator; + if (baseColumnGen === undefined) { + throw new Error(`column with type ${col.baseColumn!.columnType} is not supported for now.`); + } + generator = new GenerateArray({ baseColumnGen, size: col.size }); + + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + return generator; } - return columnPossibleGenerator; + if (col.hasDefault && col.default !== undefined) { + generator = new GenerateDefault({ + defaultValue: col.default, + }); + return generator; + } + + return generator; }; pickGeneratorForMysqlColumn = ( - columnPossibleGenerator: Prettify, table: Table, col: Column, ) => { @@ -541,8 +704,8 @@ class SeedService { (col.columnType.includes('serial') || col.columnType.includes('int')) && table.primaryKeys.includes(col.name) ) { - columnPossibleGenerator.generator = new GenerateIntPrimaryKey({}); - return columnPossibleGenerator; + const generator = new GenerateIntPrimaryKey({}); + return generator; } let minValue: number | bigint | undefined; @@ -576,17 +739,17 @@ class SeedService { } if (col.columnType.includes('int')) { - columnPossibleGenerator.generator = new GenerateInt({ + const generator = new GenerateInt({ minValue, maxValue, }); - return columnPossibleGenerator; + return generator; } if (col.columnType.includes('serial')) { - const genObj = new GenerateIntPrimaryKey({}); - genObj.maxValue = maxValue; - columnPossibleGenerator.generator = genObj; + const generator = new GenerateIntPrimaryKey({}); + generator.maxValue = maxValue; + return generator; } // NUMBER(real, double, decimal, float) @@ -596,8 +759,8 @@ class SeedService { || col.columnType === 'decimal' || col.columnType === 'float' ) { - columnPossibleGenerator.generator = new GenerateNumber({}); - return columnPossibleGenerator; + const generator = new GenerateNumber({}); + return generator; } // STRING @@ -610,8 +773,8 @@ class SeedService { || col.columnType.includes('varbinary')) && table.primaryKeys.includes(col.name) ) { - columnPossibleGenerator.generator = new GenerateUniqueString({}); - return columnPossibleGenerator; + const generator = new GenerateUniqueString({}); + return generator; } if ( @@ -623,8 +786,8 @@ class SeedService { || col.columnType.includes('varbinary')) && col.name.toLowerCase().includes('name') ) { - columnPossibleGenerator.generator = new GenerateFirstName({}); - return columnPossibleGenerator; + const generator = new GenerateFirstName({}); + return generator; } if ( @@ -636,8 +799,8 @@ class SeedService { || col.columnType.includes('varbinary')) && col.name.toLowerCase().includes('email') ) { - columnPossibleGenerator.generator = new GenerateEmail({}); - return columnPossibleGenerator; + const generator = new GenerateEmail({}); + return generator; } if ( @@ -649,61 +812,67 @@ class SeedService { || col.columnType.includes('varbinary') ) { // console.log(col, table); - columnPossibleGenerator.generator = new GenerateString({}); - return columnPossibleGenerator; + const generator = new GenerateString({}); + return generator; } // BOOLEAN if (col.columnType === 'boolean') { - columnPossibleGenerator.generator = new GenerateBoolean({}); - return columnPossibleGenerator; + const generator = new GenerateBoolean({}); + return generator; } // DATE, TIME, TIMESTAMP, DATETIME, YEAR if (col.columnType.includes('datetime')) { - columnPossibleGenerator.generator = new GenerateDatetime({}); - return columnPossibleGenerator; + const generator = new GenerateDatetime({}); + return generator; } if (col.columnType.includes('date')) { - columnPossibleGenerator.generator = new GenerateDate({}); - return columnPossibleGenerator; + const generator = new GenerateDate({}); + return generator; } if (col.columnType === 'time') { - columnPossibleGenerator.generator = new GenerateTime({}); - return columnPossibleGenerator; + const generator = new GenerateTime({}); + return generator; } if (col.columnType.includes('timestamp')) { - columnPossibleGenerator.generator = new GenerateTimestamp({}); - return columnPossibleGenerator; + const generator = new GenerateTimestamp({}); + return generator; } if (col.columnType === 'year') { - columnPossibleGenerator.generator = new GenerateYear({}); - return columnPossibleGenerator; + const generator = new GenerateYear({}); + return generator; } // JSON if (col.columnType === 'json') { - columnPossibleGenerator.generator = new GenerateJson({}); - return columnPossibleGenerator; + const generator = new GenerateJson({}); + return generator; } // ENUM if (col.enumValues !== undefined) { - columnPossibleGenerator.generator = new GenerateEnum({ + const generator = new GenerateEnum({ enumValues: col.enumValues, }); - return columnPossibleGenerator; + return generator; } - return columnPossibleGenerator; + if (col.hasDefault && col.default !== undefined) { + const generator = new GenerateDefault({ + defaultValue: col.default, + }); + return generator; + } + + return; }; pickGeneratorForSqlite = ( - columnPossibleGenerator: Prettify, table: Table, col: Column, ) => { @@ -712,8 +881,8 @@ class SeedService { (col.columnType === 'integer' || col.columnType === 'numeric') && table.primaryKeys.includes(col.name) ) { - columnPossibleGenerator.generator = new GenerateIntPrimaryKey({}); - return columnPossibleGenerator; + const generator = new GenerateIntPrimaryKey({}); + return generator; } if ( @@ -721,19 +890,19 @@ class SeedService { || col.columnType === 'numeric' || col.columnType === 'bigint' ) { - columnPossibleGenerator.generator = new GenerateInt({}); - return columnPossibleGenerator; + const generator = new GenerateInt({}); + return generator; } if (col.columnType === 'boolean') { - columnPossibleGenerator.generator = new GenerateBoolean({}); - return columnPossibleGenerator; + const generator = new GenerateBoolean({}); + return generator; } // number section ------------------------------------------------------------------------------------ if (col.columnType === 'real' || col.columnType === 'numeric') { - columnPossibleGenerator.generator = new GenerateNumber({}); - return columnPossibleGenerator; + const generator = new GenerateNumber({}); + return generator; } // string section ------------------------------------------------------------------------------------ @@ -743,8 +912,8 @@ class SeedService { || col.columnType === 'blob') && table.primaryKeys.includes(col.name) ) { - columnPossibleGenerator.generator = new GenerateUniqueString({}); - return columnPossibleGenerator; + const generator = new GenerateUniqueString({}); + return generator; } if ( @@ -753,8 +922,8 @@ class SeedService { || col.columnType === 'blob') && col.name.toLowerCase().includes('name') ) { - columnPossibleGenerator.generator = new GenerateFirstName({}); - return columnPossibleGenerator; + const generator = new GenerateFirstName({}); + return generator; } if ( @@ -763,8 +932,8 @@ class SeedService { || col.columnType === 'blob') && col.name.toLowerCase().includes('email') ) { - columnPossibleGenerator.generator = new GenerateEmail({}); - return columnPossibleGenerator; + const generator = new GenerateEmail({}); + return generator; } if ( @@ -773,32 +942,87 @@ class SeedService { || col.columnType === 'blob' || col.columnType === 'blobbuffer' ) { - columnPossibleGenerator.generator = new GenerateString({}); - return columnPossibleGenerator; + const generator = new GenerateString({}); + return generator; } if (col.columnType === 'textjson' || col.columnType === 'blobjson') { - columnPossibleGenerator.generator = new GenerateJson({}); - return columnPossibleGenerator; + const generator = new GenerateJson({}); + return generator; } if (col.columnType === 'timestamp' || col.columnType === 'timestamp_ms') { - columnPossibleGenerator.generator = new GenerateTimestamp({}); - return columnPossibleGenerator; + const generator = new GenerateTimestamp({}); + return generator; + } + + if (col.hasDefault && col.default !== undefined) { + const generator = new GenerateDefault({ + defaultValue: col.default, + }); + return generator; + } + + return; + }; + + filterCyclicTables = (tablesGenerators: ReturnType) => { + const filteredTablesGenerators = tablesGenerators.filter((tableGen) => + tableGen.columnsPossibleGenerators.some((columnGen) => + columnGen.isCyclic === true && columnGen.wasDefinedBefore === true + ) + ); + + const tablesUniqueNotNullColumn: { [tableName: string]: { uniqueNotNullColName: string } } = {}; + + for (const [idx, tableGen] of filteredTablesGenerators.entries()) { + const uniqueNotNullColName = filteredTablesGenerators[idx]!.columnsPossibleGenerators.find((colGen) => + colGen.primary === true + || (colGen.isUnique === true + && colGen.notNull === true) + )?.columnName; + if (uniqueNotNullColName === undefined) { + throw new Error( + `Table '${tableGen.tableName}' does not have primary or (unique and notNull) column. Can't seed table with cyclic relation.`, + ); + } + tablesUniqueNotNullColumn[tableGen.tableName] = { uniqueNotNullColName }; + + filteredTablesGenerators[idx]!.columnsPossibleGenerators = tableGen.columnsPossibleGenerators.filter((colGen) => + (colGen.isCyclic === true && colGen.wasDefinedBefore === true) || colGen.columnName === uniqueNotNullColName + ).map((colGen) => { + const newColGen = { ...colGen }; + newColGen.wasDefinedBefore = false; + return newColGen; + }); } - return columnPossibleGenerator; + return { filteredTablesGenerators, tablesUniqueNotNullColumn }; }; generateTablesValues = async ( - relations: Relation[], + relations: (Relation & { isCyclic: boolean })[], tablesGenerators: ReturnType, db?: | PgDatabase | MySqlDatabase | BaseSQLiteDatabase, schema?: { [key: string]: PgTable | MySqlTable | SQLiteTable }, - options?: { count?: number; seed?: number; preserveData?: boolean; insertDataInDb?: boolean }, + options?: { + count?: number; + seed?: number; + preserveData?: boolean; + preserveCyclicTablesData?: boolean; + insertDataInDb?: boolean; + updateDataInDb?: boolean; + tablesValues?: { + tableName: string; + rows: { + [columnName: string]: string | number | boolean | undefined; + }[]; + }[]; + tablesUniqueNotNullColumn?: { [tableName: string]: { uniqueNotNullColName: string } }; + }, ) => { // console.time( // "generateTablesValues-----------------------------------------------------" @@ -815,18 +1039,20 @@ class SeedService { let tablesValues: { tableName: string; rows: typeof tableValues; - }[] = []; + }[] = options?.tablesValues === undefined ? [] : options.tablesValues; let pRNGSeed: number; // relations = relations.filter(rel => rel.type === "one"); - let filteredRelations: Relation[]; + let filteredRelations: typeof relations; - let preserveData: boolean, insertDataInDb: boolean = true; + let preserveData: boolean, insertDataInDb: boolean = true, updateDataInDb: boolean = false; if (options?.preserveData !== undefined) preserveData = options.preserveData; if (options?.insertDataInDb !== undefined) insertDataInDb = options.insertDataInDb; + if (options?.updateDataInDb !== undefined) updateDataInDb = options.updateDataInDb; + if (updateDataInDb === true) insertDataInDb = false; // TODO: now I'm generating tablesInOutRelations twice, first time in generatePossibleGenerators and second time here. maybe should generate it once instead. - const tablesInOutRelations = this.getTablesInOutRelations(relations); + const { tablesInOutRelations } = this.getInfoFromRelations(relations); for (const table of tablesGenerators) { tableCount = table.count === undefined ? options?.count || this.defaultCountForTable : table.count; @@ -867,15 +1093,18 @@ class SeedService { for (let colIdx = 0; colIdx < rel.columns.length; colIdx++) { let refColumnValues: (string | number | boolean)[]; - let hasSelfRelation: boolean; + let hasSelfRelation: boolean = false; let repeatedValuesCount: | number | { weight: number; count: number | number[] }[] | undefined, weightedCountSeed: number | undefined; - let genObj: AbstractGenerator; + let genObj: AbstractGenerator | undefined; - if (rel.table === rel.refTable) { + if ( + rel.table === rel.refTable + && tableGenerators[rel.columns[colIdx]!]?.wasRefined === false + ) { const refColName = rel.refColumns[colIdx] as string; pRNGSeed = generateHashFromString( `${table.tableName}.${refColName}`, @@ -898,11 +1127,13 @@ class SeedService { genObj = new GenerateSelfRelationsValuesFromArray({ values: refColumnValues, }); - } else { + } else if ( + tableGenerators[rel.columns[colIdx]!]?.wasDefinedBefore === false + && tableGenerators[rel.columns[colIdx]!]?.wasRefined === false + ) { refColumnValues = tablesValues .find((val) => val.tableName === rel.refTable)! .rows!.map((row) => row[rel.refColumns[colIdx]!]!); - hasSelfRelation = false; if ( table.withFromTable[rel.refTable] !== undefined @@ -920,7 +1151,9 @@ class SeedService { } // console.log(rel.columns[colIdx], tableGenerators) - tableGenerators[rel.columns[colIdx]!]!.generator = genObj; + if (genObj !== undefined) { + tableGenerators[rel.columns[colIdx]!]!.generator = genObj; + } tableGenerators[rel.columns[colIdx]!] = { ...tableGenerators[rel.columns[colIdx]!]!, hasSelfRelation, @@ -937,6 +1170,9 @@ class SeedService { ? false : true; + preserveData = preserveData || (options?.preserveCyclicTablesData === true + && table.columnsPossibleGenerators.some((colGen) => colGen.isCyclic === true)); + tableValues = await this.generateColumnsValuesByGenerators({ tableGenerators, db, @@ -945,6 +1181,10 @@ class SeedService { count: tableCount, preserveData, insertDataInDb, + updateDataInDb, + uniqueNotNullColName: options?.tablesUniqueNotNullColumn === undefined + ? undefined + : options?.tablesUniqueNotNullColumn[table.tableName]?.uniqueNotNullColName, }); if (preserveData === true) { @@ -980,6 +1220,8 @@ class SeedService { count, preserveData = true, insertDataInDb = true, + updateDataInDb = false, + uniqueNotNullColName, batchSize = 10000, }: { tableGenerators: Prettify; @@ -992,12 +1234,18 @@ class SeedService { count?: number; preserveData?: boolean; insertDataInDb?: boolean; + updateDataInDb?: boolean; + uniqueNotNullColName?: string; batchSize?: number; }) => { if (count === undefined) { count = this.defaultCountForTable; } + if (updateDataInDb === true) { + batchSize = 1; + } + let columnGenerator: (typeof tableGenerators)[string]; const columnsGenerators: { [columnName: string]: AbstractGenerator; @@ -1017,20 +1265,13 @@ class SeedService { seed: columnGenerator.pRNGSeed, }); - if ( - columnsGenerators[columnName]!.uniqueVersionOfGen !== undefined - && columnsGenerators[columnName]!.isUnique === true - ) { - const uniqueGen = new columnsGenerators[columnName]!.uniqueVersionOfGen!({ - ...columnsGenerators[columnName]!.params, - }); - uniqueGen.init({ - count, - seed: columnGenerator.pRNGSeed, - }); - uniqueGen.isUnique = columnsGenerators[columnName]!.isUnique; - uniqueGen.dataType = columnsGenerators[columnName]!.dataType; + const arrayGen = columnsGenerators[columnName]!.replaceIfArray({ count, seed: columnGenerator.pRNGSeed }); + if (arrayGen !== undefined) { + columnsGenerators[columnName] = arrayGen; + } + const uniqueGen = columnsGenerators[columnName]!.replaceIfUnique({ count, seed: columnGenerator.pRNGSeed }); + if (uniqueGen !== undefined) { columnsGenerators[columnName] = uniqueGen; } } @@ -1050,7 +1291,7 @@ class SeedService { batchSize = batchSize > maxBatchSize ? maxBatchSize : batchSize; if ( - insertDataInDb === true + (insertDataInDb === true || updateDataInDb === true) && (db === undefined || schema === undefined || tableName === undefined) ) { throw new Error('db or schema or tableName is undefined.'); @@ -1077,41 +1318,75 @@ class SeedService { } if ( - insertDataInDb === true + (insertDataInDb === true || updateDataInDb === true) && ((i + 1) % batchSize === 0 || i === count - 1) ) { if (preserveData === false) { - await this.insertInDb({ - generatedValues, - db: db as - | PgDatabase - | MySqlDatabase - | BaseSQLiteDatabase, - schema: schema as { - [key: string]: PgTable | MySqlTable | SQLiteTable; - }, - tableName: tableName as string, - override, - }); + if (insertDataInDb === true) { + await this.insertInDb({ + generatedValues, + db: db as + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase, + schema: schema as { + [key: string]: PgTable | MySqlTable | SQLiteTable; + }, + tableName: tableName as string, + override, + }); + } else if (updateDataInDb === true) { + await this.updateDb({ + generatedValues, + db: db as + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase, + schema: schema as { + [key: string]: PgTable | MySqlTable | SQLiteTable; + }, + tableName: tableName as string, + uniqueNotNullColName: uniqueNotNullColName as string, + }); + } + generatedValues = []; } else { const batchCount = Math.floor(i / batchSize); - await this.insertInDb({ - generatedValues: generatedValues.slice( - batchSize * batchCount, - batchSize * (batchCount + 1), - ), - db: db as - | PgDatabase - | MySqlDatabase - | BaseSQLiteDatabase, - schema: schema as { - [key: string]: PgTable | MySqlTable | SQLiteTable; - }, - tableName: tableName as string, - override, - }); + if (insertDataInDb === true) { + await this.insertInDb({ + generatedValues: generatedValues.slice( + batchSize * batchCount, + batchSize * (batchCount + 1), + ), + db: db as + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase, + schema: schema as { + [key: string]: PgTable | MySqlTable | SQLiteTable; + }, + tableName: tableName as string, + override, + }); + } else if (updateDataInDb === true) { + await this.updateDb({ + generatedValues: generatedValues.slice( + batchSize * batchCount, + batchSize * (batchCount + 1), + ), + db: db as + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase, + schema: schema as { + [key: string]: PgTable | MySqlTable | SQLiteTable; + }, + tableName: tableName as string, + uniqueNotNullColName: uniqueNotNullColName as string, + }); + } } } } @@ -1130,7 +1405,7 @@ class SeedService { [columnName: string]: number | string | boolean | undefined; }[]; db: - | PgDatabase + | PgDatabase | MySqlDatabase | BaseSQLiteDatabase; schema: { @@ -1155,6 +1430,45 @@ class SeedService { .values(generatedValues); } }; + + updateDb = async ({ + generatedValues, + db, + schema, + tableName, + uniqueNotNullColName, + }: { + generatedValues: { + [columnName: string]: number | string | boolean | undefined; + }[]; + db: + | PgDatabase + | MySqlDatabase + | BaseSQLiteDatabase; + schema: { + [key: string]: PgTable | MySqlTable | SQLiteTable; + }; + tableName: string; + uniqueNotNullColName: string; + }) => { + if (is(db, PgDatabase)) { + const table = (schema as { [key: string]: PgTableWithColumns })[tableName]!; + const uniqueNotNullCol = table[uniqueNotNullColName]; + await db.update(table).set(generatedValues[0]!).where( + eq(uniqueNotNullCol, generatedValues[0]![uniqueNotNullColName]), + ); + } else if (is(db, MySqlDatabase)) { + const table = (schema as { [key: string]: MySqlTableWithColumns })[tableName]!; + await db.update(table).set(generatedValues[0]!).where( + eq(table[uniqueNotNullColName], generatedValues[0]![uniqueNotNullColName]), + ); + } else if (is(db, BaseSQLiteDatabase)) { + const table = (schema as { [key: string]: SQLiteTableWithColumns })[tableName]!; + await db.update(table).set(generatedValues[0]!).where( + eq(table[uniqueNotNullColName], generatedValues[0]![uniqueNotNullColName]), + ); + } + }; } export default new SeedService(); diff --git a/drizzle-seed/src/services/utils.ts b/drizzle-seed/src/services/utils.ts index 8ce3db04f..c972e7bd1 100644 --- a/drizzle-seed/src/services/utils.ts +++ b/drizzle-seed/src/services/utils.ts @@ -89,3 +89,17 @@ export const fillTemplate = ({ template, placeholdersCount, values, defaultValue return resultStr; }; + +// is variable is object-like. +// Example: +// isObject({f: 4}) === true; +// isObject([1,2,3]) === false; +// isObject(new Set()) === false; +export const isObject = (value: any) => { + if (value !== null && value !== undefined && value.constructor === Object) return true; + return false; +}; + +export const equalSets = (set1: Set, set2: Set) => { + return set1.size === set2.size && [...set1].every((si) => set2.has(si)); +}; diff --git a/drizzle-seed/src/tests/pg/allDataTypesTest/pgSchema.ts b/drizzle-seed/src/tests/pg/allDataTypesTest/pgSchema.ts deleted file mode 100644 index c8dd22355..000000000 --- a/drizzle-seed/src/tests/pg/allDataTypesTest/pgSchema.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { - bigint, - bigserial, - boolean, - char, - date, - decimal, - doublePrecision, - integer, - interval, - json, - jsonb, - line, - numeric, - pgEnum, - pgSchema, - point, - real, - serial, - smallint, - smallserial, - text, - time, - timestamp, - varchar, -} from 'drizzle-orm/pg-core'; - -export const schema = pgSchema('seeder_lib_pg'); - -export const moodEnum = pgEnum('mood_enum', ['sad', 'ok', 'happy']); - -export const allDataTypes = schema.table('all_data_types', { - integer: integer('integer'), - smallint: smallint('smallint'), - biginteger: bigint('bigint', { mode: 'bigint' }), - bigintNumber: bigint('bigint_number', { mode: 'number' }), - serial: serial('serial'), - smallserial: smallserial('smallserial'), - bigserial: bigserial('bigserial', { mode: 'bigint' }), - bigserialNumber: bigserial('bigserial_number', { mode: 'number' }), - boolean: boolean('boolean'), - text: text('text'), - varchar: varchar('varchar', { length: 256 }), - char: char('char', { length: 256 }), - numeric: numeric('numeric'), - decimal: decimal('decimal'), - real: real('real'), - doublePrecision: doublePrecision('double_precision'), - json: json('json'), - jsonb: jsonb('jsonb'), - time: time('time'), - timestampDate: timestamp('timestamp_date', { mode: 'date' }), - timestampString: timestamp('timestamp_string', { mode: 'string' }), - dateString: date('date_string', { mode: 'string' }), - date: date('date', { mode: 'date' }), - interval: interval('interval'), - point: point('point', { mode: 'xy' }), - pointTuple: point('point_tuple', { mode: 'tuple' }), - line: line('line', { mode: 'abc' }), - lineTuple: line('line_tuple', { mode: 'tuple' }), - moodEnum: moodEnum('mood_enum'), -}); diff --git a/drizzle-seed/src/tests/pg/allDataTypesTest/pg_all_data_types.test.ts b/drizzle-seed/src/tests/pg/allDataTypesTest/pg_all_data_types.test.ts deleted file mode 100644 index b876915b2..000000000 --- a/drizzle-seed/src/tests/pg/allDataTypesTest/pg_all_data_types.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -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, beforeAll, expect, test } from 'vitest'; -import { seed } from '../../../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` - DO $$ BEGIN - CREATE TYPE "seeder_lib_pg"."mood_enum" AS ENUM('sad', 'ok', 'happy'); - EXCEPTION - WHEN duplicate_object THEN null; - END $$; - `, - ); - - await db.execute( - sql` - CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."all_data_types" ( - "integer" integer, - "smallint" smallint, - "bigint" bigint, - "bigint_number" bigint, - "serial" serial NOT NULL, - "smallserial" "smallserial" NOT NULL, - "bigserial" bigserial, - "bigserial_number" bigserial NOT NULL, - "boolean" boolean, - "text" text, - "varchar" varchar(256), - "char" char(256), - "numeric" numeric, - "decimal" numeric, - "real" real, - "double_precision" double precision, - "json" json, - "jsonb" jsonb, - "time" time, - "timestamp_date" timestamp, - "timestamp_string" timestamp, - "date_string" date, - "date" date, - "interval" interval, - "point" "point", - "point_tuple" "point", - "line" "line", - "line_tuple" "line", - "mood_enum" "seeder_lib_pg"."mood_enum" - ); - `, - ); -}); - -afterAll(async () => { - await client.close(); -}); - -test('all data types test', async () => { - await seed(db, schema, { count: 10000 }); - - const allDataTypes = await db.select().from(schema.allDataTypes); - // every value in each 10 rows does not equal undefined. - const predicate = allDataTypes.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); - - expect(predicate).toBe(true); -}); diff --git a/drizzle-seed/src/types/seedService.ts b/drizzle-seed/src/types/seedService.ts index 2490d5e26..0b5237468 100644 --- a/drizzle-seed/src/types/seedService.ts +++ b/drizzle-seed/src/types/seedService.ts @@ -16,7 +16,11 @@ export type GeneratePossibleGeneratorsColumnType = { generator: AbstractGenerator | undefined; isUnique: boolean; notNull: boolean; + primary: boolean; generatedIdentityType?: 'always' | 'byDefault' | undefined; + wasRefined: boolean; + wasDefinedBefore: boolean; + isCyclic: boolean; }; export type GeneratePossibleGeneratorsTableType = Prettify<{ diff --git a/drizzle-seed/src/types/tables.ts b/drizzle-seed/src/types/tables.ts index 637e96c48..8473179ed 100644 --- a/drizzle-seed/src/types/tables.ts +++ b/drizzle-seed/src/types/tables.ts @@ -4,12 +4,15 @@ export type Column = { name: string; dataType: string; columnType: string; + size?: number; default?: any; hasDefault: boolean; enumValues?: string[]; isUnique: boolean; notNull: boolean; + primary: boolean; generatedIdentityType?: 'always' | 'byDefault' | undefined; + baseColumn?: Omit; }; export type Table = { @@ -29,6 +32,8 @@ export type Relation = { refColumns: string[]; }; +export type RelationWithReferences = Relation & { isCyclic?: boolean; refTableRels: RelationWithReferences[] }; + export type Prettify = & { [K in keyof T]: T[K]; diff --git a/drizzle-seed/src/tests/benchmarks/generatorsBenchmark.ts b/drizzle-seed/tests/benchmarks/generatorsBenchmark.ts similarity index 97% rename from drizzle-seed/src/tests/benchmarks/generatorsBenchmark.ts rename to drizzle-seed/tests/benchmarks/generatorsBenchmark.ts index cfd275da0..9d79ebaa8 100644 --- a/drizzle-seed/src/tests/benchmarks/generatorsBenchmark.ts +++ b/drizzle-seed/tests/benchmarks/generatorsBenchmark.ts @@ -1,4 +1,4 @@ -import lastNames from '../../datasets/lastNames.ts'; +import lastNames from '../../src/datasets/lastNames.ts'; import { GenerateBoolean, GenerateCity, @@ -40,7 +40,7 @@ import { GenerateValuesFromArray, GenerateYear, WeightedRandomGenerator, -} from '../../services/GeneratorsWrappers.ts'; +} from '../../src/services/GeneratorsWrappers.ts'; const benchmark = ({ generatorName, generator, count = 100000, seed = 1 }: { generatorName: string; diff --git a/drizzle-seed/src/tests/mysql/allDataTypesTest/drizzle.config.ts b/drizzle-seed/tests/mysql/allDataTypesTest/drizzle.config.ts similarity index 100% rename from drizzle-seed/src/tests/mysql/allDataTypesTest/drizzle.config.ts rename to drizzle-seed/tests/mysql/allDataTypesTest/drizzle.config.ts diff --git a/drizzle-seed/src/tests/mysql/allDataTypesTest/mysqlSchema.ts b/drizzle-seed/tests/mysql/allDataTypesTest/mysqlSchema.ts similarity index 100% rename from drizzle-seed/src/tests/mysql/allDataTypesTest/mysqlSchema.ts rename to drizzle-seed/tests/mysql/allDataTypesTest/mysqlSchema.ts diff --git a/drizzle-seed/src/tests/mysql/allDataTypesTest/mysql_all_data_types.test.ts b/drizzle-seed/tests/mysql/allDataTypesTest/mysql_all_data_types.test.ts similarity index 98% rename from drizzle-seed/src/tests/mysql/allDataTypesTest/mysql_all_data_types.test.ts rename to drizzle-seed/tests/mysql/allDataTypesTest/mysql_all_data_types.test.ts index dc663800e..5a9ba9908 100644 --- a/drizzle-seed/src/tests/mysql/allDataTypesTest/mysql_all_data_types.test.ts +++ b/drizzle-seed/tests/mysql/allDataTypesTest/mysql_all_data_types.test.ts @@ -7,7 +7,7 @@ import type { Connection } from 'mysql2/promise'; import { createConnection } from 'mysql2/promise'; import { v4 as uuid } from 'uuid'; import { afterAll, beforeAll, expect, test } from 'vitest'; -import { seed } from '../../../index.ts'; +import { seed } from '../../../src/index.ts'; import * as schema from './mysqlSchema.ts'; let mysqlContainer: Docker.Container; diff --git a/drizzle-seed/tests/mysql/cyclicTables/cyclicTables.test.ts b/drizzle-seed/tests/mysql/cyclicTables/cyclicTables.test.ts new file mode 100644 index 000000000..08fb7a0fe --- /dev/null +++ b/drizzle-seed/tests/mysql/cyclicTables/cyclicTables.test.ts @@ -0,0 +1,211 @@ +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 model + ( + id int not null + primary key, + name varchar(256) not null, + defaultImageId int null + ); + `, + ); + + await db.execute( + sql` + create table model_image + ( + id int not null + primary key, + url varchar(256) not null, + caption varchar(256) null, + modelId int not null, + constraint model_image_modelId_model_id_fk + foreign key (modelId) references model (id) + ); + `, + ); + + await db.execute( + sql` + alter table model + add constraint model_defaultImageId_model_image_id_fk + foreign key (defaultImageId) references model_image (id); + `, + ); + + // 3 tables case + await db.execute( + sql` + create table model1 + ( + id int not null + primary key, + name varchar(256) not null, + userId int null, + defaultImageId int null + ); + `, + ); + + await db.execute( + sql` + create table model_image1 + ( + id int not null + primary key, + url varchar(256) not null, + caption varchar(256) null, + modelId int not null, + constraint model_image1_modelId_model1_id_fk + foreign key (modelId) references model1 (id) + ); + `, + ); + + await db.execute( + sql` + create table user + ( + id int not null + primary key, + name text null, + invitedBy int null, + imageId int not null, + constraint user_imageId_model_image1_id_fk + foreign key (imageId) references model_image1 (id), + constraint user_invitedBy_user_id_fk + foreign key (invitedBy) references user (id) + ); + `, + ); + + await db.execute( + sql` + alter table model1 + add constraint model1_userId_user_id_fk + foreign key (userId) references user (id); + `, + ); +}); + +afterAll(async () => { + await client?.end().catch(console.error); + await mysqlContainer?.stop().catch(console.error); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +test('2 cyclic tables test', async () => { + await seed(db, { + modelTable: schema.modelTable, + modelImageTable: schema.modelImageTable, + }); + + const modelTable = await db.select().from(schema.modelTable); + const modelImageTable = await db.select().from(schema.modelImageTable); + + expect(modelTable.length).toBe(10); + let predicate = modelTable.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(modelImageTable.length).toBe(10); + predicate = modelImageTable.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('3 cyclic tables test', async () => { + await seed(db, { + modelTable1: schema.modelTable1, + modelImageTable1: schema.modelImageTable1, + user: schema.user, + }); + + const modelTable1 = await db.select().from(schema.modelTable1); + const modelImageTable1 = await db.select().from(schema.modelImageTable1); + const user = await db.select().from(schema.user); + + expect(modelTable1.length).toBe(10); + let predicate = modelTable1.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(modelImageTable1.length).toBe(10); + predicate = modelImageTable1.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(user.length).toBe(10); + predicate = user.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/mysql/cyclicTables/mysqlSchema.ts b/drizzle-seed/tests/mysql/cyclicTables/mysqlSchema.ts new file mode 100644 index 000000000..ff8149a81 --- /dev/null +++ b/drizzle-seed/tests/mysql/cyclicTables/mysqlSchema.ts @@ -0,0 +1,76 @@ +import { relations } from 'drizzle-orm'; +import type { AnyMySqlColumn } from 'drizzle-orm/mysql-core'; +import { int, mysqlTable, serial, text, varchar } from 'drizzle-orm/mysql-core'; + +// MODEL +export const modelTable = mysqlTable( + 'model', + { + id: serial().primaryKey(), + name: varchar({ length: 256 }).notNull(), + defaultImageId: int().references(() => modelImageTable.id), + }, +); + +export const modelRelations = relations(modelTable, ({ one, many }) => ({ + images: many(modelImageTable), + defaultImage: one(modelImageTable, { + fields: [modelTable.defaultImageId], + references: [modelImageTable.id], + }), +})); + +// MODEL IMAGE +export const modelImageTable = mysqlTable( + 'model_image', + { + id: serial().primaryKey(), + url: varchar({ length: 256 }).notNull(), + caption: varchar({ length: 256 }), + modelId: int() + .notNull() + .references((): AnyMySqlColumn => modelTable.id), + }, +); + +export const modelImageRelations = relations(modelImageTable, ({ one }) => ({ + model: one(modelTable, { + fields: [modelImageTable.modelId], + references: [modelTable.id], + }), +})); + +// 3 tables case +export const modelTable1 = mysqlTable( + 'model1', + { + id: serial().primaryKey(), + name: varchar({ length: 256 }).notNull(), + userId: int() + .references(() => user.id), + defaultImageId: int(), + }, +); + +export const modelImageTable1 = mysqlTable( + 'model_image1', + { + id: serial().primaryKey(), + url: varchar({ length: 256 }).notNull(), + caption: varchar({ length: 256 }), + modelId: int().notNull() + .references((): AnyMySqlColumn => modelTable1.id), + }, +); + +export const user = mysqlTable( + 'user', + { + id: serial().primaryKey(), + name: text(), + invitedBy: int().references((): AnyMySqlColumn => user.id), + imageId: int() + .notNull() + .references((): AnyMySqlColumn => modelImageTable1.id), + }, +); diff --git a/drizzle-seed/src/tests/mysql/drizzle.config.ts b/drizzle-seed/tests/mysql/drizzle.config.ts similarity index 100% rename from drizzle-seed/src/tests/mysql/drizzle.config.ts rename to drizzle-seed/tests/mysql/drizzle.config.ts diff --git a/drizzle-seed/src/tests/mysql/generatorsTest/drizzle.config.ts b/drizzle-seed/tests/mysql/generatorsTest/drizzle.config.ts similarity index 100% rename from drizzle-seed/src/tests/mysql/generatorsTest/drizzle.config.ts rename to drizzle-seed/tests/mysql/generatorsTest/drizzle.config.ts diff --git a/drizzle-seed/src/tests/mysql/generatorsTest/generators.test.ts b/drizzle-seed/tests/mysql/generatorsTest/generators.test.ts similarity index 98% rename from drizzle-seed/src/tests/mysql/generatorsTest/generators.test.ts rename to drizzle-seed/tests/mysql/generatorsTest/generators.test.ts index 223d5b54e..2bef885da 100644 --- a/drizzle-seed/src/tests/mysql/generatorsTest/generators.test.ts +++ b/drizzle-seed/tests/mysql/generatorsTest/generators.test.ts @@ -7,7 +7,7 @@ import type { Connection } from 'mysql2/promise'; import { createConnection } from 'mysql2/promise'; import { v4 as uuid } from 'uuid'; import { afterAll, beforeAll, expect, test } from 'vitest'; -import { seed } from '../../../index.ts'; +import { seed } from '../../../src/index.ts'; import * as schema from './mysqlSchema.ts'; let mysqlContainer: Docker.Container; diff --git a/drizzle-seed/src/tests/mysql/generatorsTest/mysqlSchema.ts b/drizzle-seed/tests/mysql/generatorsTest/mysqlSchema.ts similarity index 100% rename from drizzle-seed/src/tests/mysql/generatorsTest/mysqlSchema.ts rename to drizzle-seed/tests/mysql/generatorsTest/mysqlSchema.ts diff --git a/drizzle-seed/src/tests/mysql/mysql.test.ts b/drizzle-seed/tests/mysql/mysql.test.ts similarity index 99% rename from drizzle-seed/src/tests/mysql/mysql.test.ts rename to drizzle-seed/tests/mysql/mysql.test.ts index d1b6790ec..7d6bfd48e 100644 --- a/drizzle-seed/src/tests/mysql/mysql.test.ts +++ b/drizzle-seed/tests/mysql/mysql.test.ts @@ -7,7 +7,7 @@ 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 '../../index.ts'; +import { reset, seed } from '../../src/index.ts'; import * as schema from './mysqlSchema.ts'; let mysqlContainer: Docker.Container; diff --git a/drizzle-seed/src/tests/mysql/mysqlSchema.ts b/drizzle-seed/tests/mysql/mysqlSchema.ts similarity index 100% rename from drizzle-seed/src/tests/mysql/mysqlSchema.ts rename to drizzle-seed/tests/mysql/mysqlSchema.ts diff --git a/drizzle-seed/src/tests/northwind/mysqlSchema.ts b/drizzle-seed/tests/northwind/mysqlSchema.ts similarity index 100% rename from drizzle-seed/src/tests/northwind/mysqlSchema.ts rename to drizzle-seed/tests/northwind/mysqlSchema.ts diff --git a/drizzle-seed/src/tests/northwind/mysqlTest.ts b/drizzle-seed/tests/northwind/mysqlTest.ts similarity index 99% rename from drizzle-seed/src/tests/northwind/mysqlTest.ts rename to drizzle-seed/tests/northwind/mysqlTest.ts index 98c2dd742..1cbdb7704 100644 --- a/drizzle-seed/src/tests/northwind/mysqlTest.ts +++ b/drizzle-seed/tests/northwind/mysqlTest.ts @@ -7,7 +7,7 @@ import mysql from 'mysql2/promise'; import * as schema from './mysqlSchema.ts'; -import { seed } from '../../index.ts'; +import { seed } from '../../src/index.ts'; const { Mysql_HOST, Mysql_PORT, Mysql_DATABASE, Mysql_USER, Mysql_PASSWORD } = process.env; diff --git a/drizzle-seed/src/tests/northwind/pgSchema.ts b/drizzle-seed/tests/northwind/pgSchema.ts similarity index 100% rename from drizzle-seed/src/tests/northwind/pgSchema.ts rename to drizzle-seed/tests/northwind/pgSchema.ts diff --git a/drizzle-seed/src/tests/northwind/pgTest.ts b/drizzle-seed/tests/northwind/pgTest.ts similarity index 99% rename from drizzle-seed/src/tests/northwind/pgTest.ts rename to drizzle-seed/tests/northwind/pgTest.ts index 284f28957..84c366b6c 100644 --- a/drizzle-seed/src/tests/northwind/pgTest.ts +++ b/drizzle-seed/tests/northwind/pgTest.ts @@ -5,7 +5,7 @@ import { drizzle } from 'drizzle-orm/node-postgres'; import { migrate } from 'drizzle-orm/node-postgres/migrator'; import { Pool as PgPool } from 'pg'; -import { seed } from '../../index.ts'; +import { seed } from '../../src/index.ts'; import * as schema from './pgSchema.ts'; const { PG_HOST, PG_PORT, PG_DATABASE, PG_USER, PG_PASSWORD } = process.env; diff --git a/drizzle-seed/src/tests/northwind/sqliteSchema.ts b/drizzle-seed/tests/northwind/sqliteSchema.ts similarity index 100% rename from drizzle-seed/src/tests/northwind/sqliteSchema.ts rename to drizzle-seed/tests/northwind/sqliteSchema.ts diff --git a/drizzle-seed/src/tests/northwind/sqliteTest.ts b/drizzle-seed/tests/northwind/sqliteTest.ts similarity index 99% rename from drizzle-seed/src/tests/northwind/sqliteTest.ts rename to drizzle-seed/tests/northwind/sqliteTest.ts index d64f2c447..0267bc288 100644 --- a/drizzle-seed/src/tests/northwind/sqliteTest.ts +++ b/drizzle-seed/tests/northwind/sqliteTest.ts @@ -5,7 +5,7 @@ import betterSqlite3 from 'better-sqlite3'; import { drizzle } from 'drizzle-orm/better-sqlite3'; import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; -import { seed } from '../../index.ts'; +import { seed } from '../../src/index.ts'; import * as schema from './sqliteSchema.ts'; const { Sqlite_PATH } = process.env; diff --git a/drizzle-seed/src/tests/pg/allDataTypesTest/drizzle.config.ts b/drizzle-seed/tests/pg/allDataTypesTest/drizzle.config.ts similarity index 100% rename from drizzle-seed/src/tests/pg/allDataTypesTest/drizzle.config.ts rename to drizzle-seed/tests/pg/allDataTypesTest/drizzle.config.ts diff --git a/drizzle-seed/tests/pg/allDataTypesTest/pgSchema.ts b/drizzle-seed/tests/pg/allDataTypesTest/pgSchema.ts new file mode 100644 index 000000000..75ba20a43 --- /dev/null +++ b/drizzle-seed/tests/pg/allDataTypesTest/pgSchema.ts @@ -0,0 +1,99 @@ +import { + bigint, + bigserial, + boolean, + char, + date, + decimal, + doublePrecision, + integer, + interval, + json, + jsonb, + line, + numeric, + pgEnum, + pgSchema, + point, + real, + serial, + smallint, + smallserial, + text, + time, + timestamp, + uuid, + varchar, +} from 'drizzle-orm/pg-core'; + +export const schema = pgSchema('seeder_lib_pg'); + +export const moodEnum = pgEnum('mood_enum', ['sad', 'ok', 'happy']); + +export const allDataTypes = schema.table('all_data_types', { + integer: integer('integer'), + smallint: smallint('smallint'), + biginteger: bigint('bigint', { mode: 'bigint' }), + bigintNumber: bigint('bigint_number', { mode: 'number' }), + serial: serial('serial'), + smallserial: smallserial('smallserial'), + bigserial: bigserial('bigserial', { mode: 'bigint' }), + bigserialNumber: bigserial('bigserial_number', { mode: 'number' }), + boolean: boolean('boolean'), + text: text('text'), + varchar: varchar('varchar', { length: 256 }), + char: char('char', { length: 256 }), + numeric: numeric('numeric'), + decimal: decimal('decimal'), + real: real('real'), + doublePrecision: doublePrecision('double_precision'), + json: json('json'), + jsonb: jsonb('jsonb'), + time: time('time'), + timestampDate: timestamp('timestamp_date', { mode: 'date' }), + timestampString: timestamp('timestamp_string', { mode: 'string' }), + dateString: date('date_string', { mode: 'string' }), + date: date('date', { mode: 'date' }), + interval: interval('interval'), + point: point('point', { mode: 'xy' }), + pointTuple: point('point_tuple', { mode: 'tuple' }), + line: line('line', { mode: 'abc' }), + lineTuple: line('line_tuple', { mode: 'tuple' }), + moodEnum: moodEnum('mood_enum'), + uuid: uuid('uuid'), +}); + +export const allArrayDataTypes = schema.table('all_array_data_types', { + integerArray: integer('integer_array').array(), + smallintArray: smallint('smallint_array').array(), + bigintegerArray: bigint('bigint_array', { mode: 'bigint' }).array(), + bigintNumberArray: bigint('bigint_number_array', { mode: 'number' }).array(), + booleanArray: boolean('boolean_array').array(), + textArray: text('text_array').array(), + varcharArray: varchar('varchar_array', { length: 256 }).array(), + charArray: char('char_array', { length: 256 }).array(), + numericArray: numeric('numeric_array').array(), + decimalArray: decimal('decimal_array').array(), + realArray: real('real_array').array(), + doublePrecisionArray: doublePrecision('double_precision_array').array(), + jsonArray: json('json_array').array(), + jsonbArray: jsonb('jsonb_array').array(), + timeArray: time('time_array').array(), + timestampDateArray: timestamp('timestamp_date_array', { mode: 'date' }).array(), + timestampStringArray: timestamp('timestamp_string_array', { mode: 'string' }).array(), + dateStringArray: date('date_string_array', { mode: 'string' }).array(), + dateArray: date('date_array', { mode: 'date' }).array(), + intervalArray: interval('interval_array').array(), + pointArray: point('point_array', { mode: 'xy' }).array(), + pointTupleArray: point('point_tuple_array', { mode: 'tuple' }).array(), + lineArray: line('line_array', { mode: 'abc' }).array(), + lineTupleArray: line('line_tuple_array', { mode: 'tuple' }).array(), + moodEnumArray: moodEnum('mood_enum_array').array(), +}); + +export const ndArrays = schema.table('nd_arrays', { + integer1DArray: integer('integer_1d_array').array(3), + integer2DArray: integer('integer_2d_array').array(3).array(4), + integer3DArray: integer('integer_3d_array').array(3).array(4).array(5), + integer4DArray: integer('integer_4d_array').array(3).array(4).array(5).array(6), +}); diff --git a/drizzle-seed/tests/pg/allDataTypesTest/pg_all_data_types.test.ts b/drizzle-seed/tests/pg/allDataTypesTest/pg_all_data_types.test.ts new file mode 100644 index 000000000..7dfbc089b --- /dev/null +++ b/drizzle-seed/tests/pg/allDataTypesTest/pg_all_data_types.test.ts @@ -0,0 +1,159 @@ +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, beforeAll, expect, test } from 'vitest'; +import { 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 if not exists "seeder_lib_pg";`); + + await db.execute( + sql` + DO $$ BEGIN + CREATE TYPE "seeder_lib_pg"."mood_enum" AS ENUM('sad', 'ok', 'happy'); + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."all_data_types" ( + "integer" integer, + "smallint" smallint, + "bigint" bigint, + "bigint_number" bigint, + "serial" serial, + "smallserial" smallserial, + "bigserial" bigserial, + "bigserial_number" bigserial, + "boolean" boolean, + "text" text, + "varchar" varchar(256), + "char" char(256), + "numeric" numeric, + "decimal" numeric, + "real" real, + "double_precision" double precision, + "json" json, + "jsonb" jsonb, + "time" time, + "timestamp_date" timestamp, + "timestamp_string" timestamp, + "date_string" date, + "date" date, + "interval" interval, + "point" "point", + "point_tuple" "point", + "line" "line", + "line_tuple" "line", + "mood_enum" "seeder_lib_pg"."mood_enum", + "uuid" "uuid" + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."all_array_data_types" ( + "integer_array" integer[], + "smallint_array" smallint[], + "bigint_array" bigint[], + "bigint_number_array" bigint[], + "boolean_array" boolean[], + "text_array" text[], + "varchar_array" varchar(256)[], + "char_array" char(256)[], + "numeric_array" numeric[], + "decimal_array" numeric[], + "real_array" real[], + "double_precision_array" double precision[], + "json_array" json[], + "jsonb_array" jsonb[], + "time_array" time[], + "timestamp_date_array" timestamp[], + "timestamp_string_array" timestamp[], + "date_string_array" date[], + "date_array" date[], + "interval_array" interval[], + "point_array" "point"[], + "point_tuple_array" "point"[], + "line_array" "line"[], + "line_tuple_array" "line"[], + "mood_enum_array" "seeder_lib_pg"."mood_enum"[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."nd_arrays" ( + "integer_1d_array" integer[3], + "integer_2d_array" integer[3][4], + "integer_3d_array" integer[3][4][5], + "integer_4d_array" integer[3][4][5][6] + ); + `, + ); +}); + +afterAll(async () => { + await client.close(); +}); + +test('all data types test', async () => { + await seed(db, { allDataTypes: schema.allDataTypes }, { count: 10000 }); + + const allDataTypes = await db.select().from(schema.allDataTypes); + // every value in each rows does not equal undefined. + const predicate = allDataTypes.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + + expect(predicate).toBe(true); +}); + +test('all array data types test', async () => { + await seed(db, { allArrayDataTypes: schema.allArrayDataTypes }, { count: 1000 }); + + const allArrayDataTypes = await db.select().from(schema.allArrayDataTypes); + // every value in each rows does not equal undefined. + const predicate = allArrayDataTypes.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && val.length === 10) + ); + + expect(predicate).toBe(true); +}); + +test('nd arrays', async () => { + await seed(db, { ndArrays: schema.ndArrays }, { count: 1000 }); + + const ndArrays = await db.select().from(schema.ndArrays); + // every value in each rows does not equal undefined. + const predicate0 = ndArrays.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && val.length !== 0) + ); + let predicate1 = true, predicate2 = true, predicate3 = true, predicate4 = true; + + for (const row of ndArrays) { + predicate1 = predicate1 && (row.integer1DArray?.length === 3); + + predicate2 = predicate2 && (row.integer2DArray?.length === 4) && (row.integer2DArray[0]?.length === 3); + + predicate3 = predicate3 && (row.integer3DArray?.length === 5) && (row.integer3DArray[0]?.length === 4) + && (row.integer3DArray[0][0]?.length === 3); + + predicate4 = predicate4 && (row.integer4DArray?.length === 6) && (row.integer4DArray[0]?.length === 5) + && (row.integer4DArray[0][0]?.length === 4) && (row.integer4DArray[0][0][0]?.length === 3); + } + + expect(predicate0 && predicate1 && predicate2 && predicate3 && predicate4).toBe(true); +}); diff --git a/drizzle-seed/tests/pg/cyclicTables/cyclicTables.test.ts b/drizzle-seed/tests/pg/cyclicTables/cyclicTables.test.ts new file mode 100644 index 000000000..c4be3509e --- /dev/null +++ b/drizzle-seed/tests/pg/cyclicTables/cyclicTables.test.ts @@ -0,0 +1,157 @@ +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 table model_image + ( + id serial + primary key, + url varchar not null, + caption varchar, + "modelId" integer not null + ); + `, + ); + + await db.execute( + sql` + create table model + ( + id serial + primary key, + name varchar not null, + "defaultImageId" integer + constraint "model_defaultImageId_model_image_id_fk" + references model_image + ); + `, + ); + + await db.execute( + sql` + alter table model_image + add constraint "model_image_modelId_model_id_fk" + foreign key ("modelId") references model; + `, + ); + + // 3 tables case + await db.execute( + sql` + create table model_image1 + ( + id serial + primary key, + url varchar not null, + caption varchar, + "modelId" integer not null + ); + `, + ); + + await db.execute( + sql` + create table "user" + ( + id serial + primary key, + name text, + "invitedBy" integer + constraint "user_invitedBy_user_id_fk" + references "user", + "imageId" integer not null + constraint "user_imageId_model_image1_id_fk" + references model_image1 + ); + `, + ); + + await db.execute( + sql` + create table model1 + ( + id serial + primary key, + name varchar not null, + "userId" integer + constraint "model1_userId_user_id_fk" + references "user", + "defaultImageId" integer + constraint "model1_defaultImageId_model_image1_id_fk" + references model_image1 + ); + `, + ); + + await db.execute( + sql` + alter table model_image1 + add constraint "model_image1_modelId_model1_id_fk" + foreign key ("modelId") references model1; + `, + ); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +afterAll(async () => { + await client.close(); +}); + +test('2 cyclic tables test', async () => { + await seed(db, { + modelTable: schema.modelTable, + modelImageTable: schema.modelImageTable, + }); + + const modelTable = await db.select().from(schema.modelTable); + const modelImageTable = await db.select().from(schema.modelImageTable); + + expect(modelTable.length).toBe(10); + let predicate = modelTable.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(modelImageTable.length).toBe(10); + predicate = modelImageTable.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('3 cyclic tables test', async () => { + await seed(db, { + modelTable1: schema.modelTable1, + modelImageTable1: schema.modelImageTable1, + user: schema.user, + }); + + const modelTable1 = await db.select().from(schema.modelTable1); + const modelImageTable1 = await db.select().from(schema.modelImageTable1); + const user = await db.select().from(schema.user); + + expect(modelTable1.length).toBe(10); + let predicate = modelTable1.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(modelImageTable1.length).toBe(10); + predicate = modelImageTable1.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(user.length).toBe(10); + predicate = user.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/pg/cyclicTables/pgSchema.ts b/drizzle-seed/tests/pg/cyclicTables/pgSchema.ts new file mode 100644 index 000000000..7615470b0 --- /dev/null +++ b/drizzle-seed/tests/pg/cyclicTables/pgSchema.ts @@ -0,0 +1,88 @@ +import { relations } from 'drizzle-orm'; +import type { AnyPgColumn } from 'drizzle-orm/pg-core'; +import { foreignKey, integer, pgTable, serial, text, varchar } from 'drizzle-orm/pg-core'; + +// MODEL +export const modelTable = pgTable( + 'model', + { + id: serial().primaryKey(), + name: varchar().notNull(), + defaultImageId: integer(), + }, + (t) => [ + foreignKey({ + columns: [t.defaultImageId], + foreignColumns: [modelImageTable.id], + }), + ], +); + +export const modelRelations = relations(modelTable, ({ one, many }) => ({ + images: many(modelImageTable), + defaultImage: one(modelImageTable, { + fields: [modelTable.defaultImageId], + references: [modelImageTable.id], + }), +})); + +// MODEL IMAGE +export const modelImageTable = pgTable( + 'model_image', + { + id: serial().primaryKey(), + url: varchar().notNull(), + caption: varchar(), + modelId: integer() + .notNull() + .references((): AnyPgColumn => modelTable.id), + }, +); + +export const modelImageRelations = relations(modelImageTable, ({ one }) => ({ + model: one(modelTable, { + fields: [modelImageTable.modelId], + references: [modelTable.id], + }), +})); + +// 3 tables case +export const modelTable1 = pgTable( + 'model1', + { + id: serial().primaryKey(), + name: varchar().notNull(), + userId: integer() + .references(() => user.id), + defaultImageId: integer(), + }, + (t) => [ + foreignKey({ + columns: [t.defaultImageId], + foreignColumns: [modelImageTable1.id], + }), + ], +); + +export const modelImageTable1 = pgTable( + 'model_image1', + { + id: serial().primaryKey(), + url: varchar().notNull(), + caption: varchar(), + modelId: integer().notNull() + .references((): AnyPgColumn => modelTable1.id), + }, +); + +export const user = pgTable( + 'user', + { + id: serial().primaryKey(), + name: text(), + invitedBy: integer().references((): AnyPgColumn => user.id), + imageId: integer() + .notNull() + .references((): AnyPgColumn => modelImageTable1.id), + }, +); diff --git a/drizzle-seed/src/tests/pg/drizzle.config.ts b/drizzle-seed/tests/pg/drizzle.config.ts similarity index 100% rename from drizzle-seed/src/tests/pg/drizzle.config.ts rename to drizzle-seed/tests/pg/drizzle.config.ts diff --git a/drizzle-seed/src/tests/pg/generatorsTest/drizzle.config.ts b/drizzle-seed/tests/pg/generatorsTest/drizzle.config.ts similarity index 100% rename from drizzle-seed/src/tests/pg/generatorsTest/drizzle.config.ts rename to drizzle-seed/tests/pg/generatorsTest/drizzle.config.ts diff --git a/drizzle-seed/src/tests/pg/generatorsTest/generators.test.ts b/drizzle-seed/tests/pg/generatorsTest/generators.test.ts similarity index 66% rename from drizzle-seed/src/tests/pg/generatorsTest/generators.test.ts rename to drizzle-seed/tests/pg/generatorsTest/generators.test.ts index 575b7a69a..3de2ce99e 100644 --- a/drizzle-seed/src/tests/pg/generatorsTest/generators.test.ts +++ b/drizzle-seed/tests/pg/generatorsTest/generators.test.ts @@ -4,14 +4,14 @@ import { PGlite } from '@electric-sql/pglite'; import type { PgliteDatabase } from 'drizzle-orm/pglite'; import { drizzle } from 'drizzle-orm/pglite'; -import { reset, seed } from '../../../index.ts'; +import { reset, seed } from '../../../src/index.ts'; import * as schema from './pgSchema.ts'; import { sql } from 'drizzle-orm'; -import cities from '../../../datasets/cityNames.ts'; -import countries from '../../../datasets/countries.ts'; -import firstNames from '../../../datasets/firstNames.ts'; -import lastNames from '../../../datasets/lastNames.ts'; +import cities from '../../../src/datasets/cityNames.ts'; +import countries from '../../../src/datasets/countries.ts'; +import firstNames from '../../../src/datasets/firstNames.ts'; +import lastNames from '../../../src/datasets/lastNames.ts'; let client: PGlite; let db: PgliteDatabase; @@ -33,6 +33,22 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."default_table" ( + "default_string" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."default_array_table" ( + "default_string" text[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."boolean_table" ( @@ -41,6 +57,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."boolean_array_table" ( + "boolean" boolean[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."city_table" ( @@ -58,6 +82,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."city_array_table" ( + "city" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."company_name_table" ( @@ -75,6 +107,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."company_name_array_table" ( + "company_name" text[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."country_table" ( @@ -92,6 +132,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."country_array_table" ( + "country" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."date_table" ( @@ -102,8 +150,9 @@ beforeAll(async () => { await db.execute( sql` - CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."default_table" ( - "default_string" text + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."date_array_table" ( + "date" date[], + "date_string" date[] ); `, ); @@ -117,6 +166,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."email_array_table" ( + "email" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."enum_table" ( @@ -142,6 +199,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."first_name_array_table" ( + "first_name" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."full_name__table" ( @@ -159,6 +224,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."full_name_array_table" ( + "full_name" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."int_primary_key_table" ( @@ -185,6 +258,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."int_array_table" ( + "int" integer[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."interval_table" ( @@ -202,6 +283,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."interval_array_table" ( + "interval" interval[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."job_Title_table" ( @@ -210,6 +299,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."job_title_array_table" ( + "job_title" text[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."json_table" ( @@ -218,6 +315,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."json_array_table" ( + "json" json[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."last_name_table" ( @@ -235,6 +340,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."last_name_array_table" ( + "last_name" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."line_table" ( @@ -243,6 +356,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."line_array_table" ( + "line" "line"[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."lorem_ipsum_table" ( @@ -251,6 +372,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."lorem_ipsum_array_table" ( + "lorem_ipsum" text[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."number_table" ( @@ -268,6 +397,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."number_array_table" ( + "number" real[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."phone_number_table" ( @@ -281,6 +418,16 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."phone_number_array_table" ( + "phoneNumber" varchar(256)[], + "phone_number_template" varchar(256)[], + "phone_number_prefixes" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."point_table" ( @@ -289,6 +436,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."point_array_table" ( + "point" "point"[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."postcode_table" ( @@ -306,6 +461,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."postcode_array_table" ( + "postcode" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."state_table" ( @@ -314,6 +477,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."state_array_table" ( + "state" text[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."street_address_table" ( @@ -331,6 +502,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."street_address_array_table" ( + "street_address" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."string_table" ( @@ -348,6 +527,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."string_array_table" ( + "string" text[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."time_table" ( @@ -356,6 +543,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."time_array_table" ( + "time" time[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."timestamp_table" ( @@ -364,6 +559,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."timestamp_array_table" ( + "timestamp" timestamp[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."values_from_array_table" ( @@ -388,6 +591,14 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."values_from_array_array_table" ( + "values_from_array" varchar(256) + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."weighted_random_table" ( @@ -404,13 +615,29 @@ beforeAll(async () => { ); `, ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."uuid_table" ( + "uuid" uuid + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."uuid_array_table" ( + "uuid" uuid[] + ); + `, + ); }); afterAll(async () => { await client.close(); }); -const count = 10000; +const count = 1000; test('enum generator test', async () => { await seed(db, { enumTable: schema.enumTable }).refine(() => ({ @@ -443,6 +670,23 @@ test('default generator test', async () => { expect(predicate).toBe(true); }); +test('default array generator test', async () => { + await seed(db, { defaultTable: schema.defaultArrayTable }).refine((funcs) => ({ + defaultTable: { + count, + columns: { + defaultString: funcs.default({ defaultValue: 'default string', arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.defaultArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('valuesFromArray generator test', async () => { await seed(db, { valuesFromArrayTable: schema.valuesFromArrayTable }).refine((funcs) => ({ valuesFromArrayTable: { @@ -555,6 +799,23 @@ test('valuesFromArray unique generator test', async () => { ).rejects.toThrow('There are no enough values to fill unique column.'); }); +test('valuesFromArray array generator test', async () => { + await seed(db, { valuesFromArrayTable: schema.valuesFromArrayArrayTable }).refine((funcs) => ({ + valuesFromArrayTable: { + count, + columns: { + valuesFromArray: funcs.valuesFromArray({ values: lastNames, arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.valuesFromArrayArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('intPrimaryKey generator test', async () => { await seed(db, { intPrimaryKeyTable: schema.intPrimaryKeyTable }).refine((funcs) => ({ intPrimaryKeyTable: { @@ -619,6 +880,23 @@ test('number unique generator test', async () => { ).rejects.toThrow('count exceeds max number of unique integers in given range(min, max), try to make range wider.'); }); +test('number array generator test', async () => { + await seed(db, { numberTable: schema.numberArrayTable }).refine((funcs) => ({ + numberTable: { + count, + columns: { + number: funcs.number({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.numberArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('int generator test', async () => { await seed(db, { intTable: schema.intTable }).refine((funcs) => ({ intTable: { @@ -664,6 +942,23 @@ test('int unique generator test', async () => { ).rejects.toThrow('count exceeds max number of unique integers in given range(min, max), try to make range wider.'); }); +test('int array generator test', async () => { + await seed(db, { intTable: schema.intArrayTable }).refine((funcs) => ({ + intTable: { + count, + columns: { + int: funcs.int({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.intArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('boolean generator test', async () => { await seed(db, { booleanTable: schema.booleanTable }).refine((funcs) => ({ booleanTable: { @@ -681,74 +976,162 @@ test('boolean generator test', async () => { expect(predicate).toBe(true); }); -test('date generator test', async () => { - await seed(db, { dateTable: schema.dateTable }).refine((funcs) => ({ - dateTable: { +test('boolean array generator test', async () => { + await seed(db, { booleanTable: schema.booleanArrayTable }).refine((funcs) => ({ + booleanTable: { count, columns: { - date: funcs.date(), + boolean: funcs.boolean({ arraySize: 3 }), }, }, })); - const data = await db.select().from(schema.dateTable); + const data = await db.select().from(schema.booleanArrayTable); // every value in each row does not equal undefined. const predicate = data.length !== 0 - && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); expect(predicate).toBe(true); }); -test('time generator test', async () => { - await seed(db, { timeTable: schema.timeTable }).refine((funcs) => ({ - timeTable: { +test('date generator test', async () => { + await seed(db, { dateTable: schema.dateTable }).refine((funcs) => ({ + dateTable: { count, columns: { - time: funcs.time(), + date: funcs.date(), }, }, })); - const data = await db.select().from(schema.timeTable); + const data = await db.select().from(schema.dateTable); // every value in each row does not equal undefined. const predicate = data.length !== 0 && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); expect(predicate).toBe(true); }); -test('timestamp generator test', async () => { - await seed(db, { timestampTable: schema.timestampTable }).refine((funcs) => ({ - timestampTable: { +test('date array generator test', async () => { + await seed(db, { dateTable: schema.dateArrayTable }).refine((funcs) => ({ + dateTable: { count, columns: { - timestamp: funcs.timestamp(), + date: funcs.date({ arraySize: 3 }), + dateString: funcs.date({ arraySize: 4 }), }, }, })); - const data = await db.select().from(schema.timestampTable); + const data = await db.select().from(schema.dateArrayTable); // every value in each row does not equal undefined. const predicate = data.length !== 0 - && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + && data.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && [3, 4].includes(val.length)) + ); expect(predicate).toBe(true); }); -test('json generator test', async () => { - await seed(db, { jsonTable: schema.jsonTable }).refine((funcs) => ({ - jsonTable: { +test('time generator test', async () => { + await seed(db, { timeTable: schema.timeTable }).refine((funcs) => ({ + timeTable: { count, columns: { - json: funcs.json(), + time: funcs.time(), }, }, })); - const data = await db.select().from(schema.jsonTable); + const data = await db.select().from(schema.timeTable); // every value in each row does not equal undefined. const predicate = data.length !== 0 && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); expect(predicate).toBe(true); }); +test('time array generator test', async () => { + await seed(db, { timeTable: schema.timeArrayTable }).refine((funcs) => ({ + timeTable: { + count, + columns: { + time: funcs.time({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.timeArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('timestamp generator test', async () => { + await seed(db, { timestampTable: schema.timestampTable }).refine((funcs) => ({ + timestampTable: { + count, + columns: { + timestamp: funcs.timestamp(), + }, + }, + })); + + const data = await db.select().from(schema.timestampTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('timestamp array generator test', async () => { + await seed(db, { timestampTable: schema.timestampArrayTable }).refine((funcs) => ({ + timestampTable: { + count, + columns: { + timestamp: funcs.timestamp({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.timestampArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + +test('json generator test', async () => { + await seed(db, { jsonTable: schema.jsonTable }).refine((funcs) => ({ + jsonTable: { + count, + columns: { + json: funcs.json(), + }, + }, + })); + + const data = await db.select().from(schema.jsonTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('json array generator test', async () => { + await seed(db, { jsonTable: schema.jsonArrayTable }).refine((funcs) => ({ + jsonTable: { + count, + columns: { + json: funcs.json({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.jsonArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('interval generator test', async () => { await seed(db, { intervalTable: schema.intervalTable }).refine((funcs) => ({ intervalTable: { @@ -783,6 +1166,23 @@ test('interval unique generator test', async () => { expect(predicate).toBe(true); }); +test('interval array generator test', async () => { + await seed(db, { intervalTable: schema.intervalArrayTable }).refine((funcs) => ({ + intervalTable: { + count, + columns: { + interval: funcs.interval({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.intervalArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('string generator test', async () => { await seed(db, { stringTable: schema.stringTable }).refine((funcs) => ({ stringTable: { @@ -816,6 +1216,23 @@ test('string unique generator test', async () => { expect(predicate).toBe(true); }); +test('string array generator test', async () => { + await seed(db, { stringTable: schema.stringArrayTable }).refine((funcs) => ({ + stringTable: { + count, + columns: { + string: funcs.string({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.stringArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('email generator test', async () => { await seed(db, { emailTable: schema.emailTable }).refine((funcs) => ({ emailTable: { @@ -833,6 +1250,23 @@ test('email generator test', async () => { expect(predicate).toBe(true); }); +test('email array generator test', async () => { + await seed(db, { emailTable: schema.emailArrayTable }).refine((funcs) => ({ + emailTable: { + count, + columns: { + email: funcs.email({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.emailArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('firstName generator test', async () => { await seed(db, { firstNameTable: schema.firstNameTable }).refine((funcs) => ({ firstNameTable: { @@ -878,6 +1312,23 @@ test('firstName unique generator test', async () => { ).rejects.toThrow('count exceeds max number of unique first names.'); }); +test('firstName array generator test', async () => { + await seed(db, { firstNameTable: schema.firstNameArrayTable }).refine((funcs) => ({ + firstNameTable: { + count, + columns: { + firstName: funcs.firstName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.firstNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('lastName generator test', async () => { await seed(db, { lastNameTable: schema.lastNameTable }).refine((funcs) => ({ lastNameTable: { @@ -923,6 +1374,23 @@ test('lastName unique generator test', async () => { ).rejects.toThrow('count exceeds max number of unique last names.'); }); +test('lastName array generator test', async () => { + await seed(db, { lastNameTable: schema.lastNameArrayTable }).refine((funcs) => ({ + lastNameTable: { + count, + columns: { + lastName: funcs.lastName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.lastNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('fullName generator test', async () => { await seed(db, { fullNameTable: schema.fullNameTable }).refine((funcs) => ({ fullNameTable: { @@ -958,6 +1426,23 @@ test('fullName unique generator test', async () => { expect(predicate).toBe(true); }); +test('fullName array generator test', async () => { + await seed(db, { fullNameTable: schema.fullNameArrayTable }).refine((funcs) => ({ + fullNameTable: { + count, + columns: { + fullName: funcs.fullName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.fullNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('country generator test', async () => { await seed(db, { countryTable: schema.countryTable }).refine((funcs) => ({ countryTable: { @@ -1003,6 +1488,23 @@ test('country unique generator test', async () => { ).rejects.toThrow('count exceeds max number of unique countries.'); }); +test('country array generator test', async () => { + await seed(db, { countryTable: schema.countryArrayTable }).refine((funcs) => ({ + countryTable: { + count, + columns: { + country: funcs.country({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.countryArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('city generator test', async () => { await seed(db, { cityTable: schema.cityTable }).refine((funcs) => ({ cityTable: { @@ -1049,6 +1551,23 @@ test('city unique generator test', async () => { ).rejects.toThrow('count exceeds max number of unique cities.'); }); +test('city array generator test', async () => { + await seed(db, { cityTable: schema.cityArrayTable }).refine((funcs) => ({ + cityTable: { + count, + columns: { + city: funcs.city({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.cityArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('streetAddress generator test', async () => { await seed(db, { streetAddressTable: schema.streetAddressTable }).refine((funcs) => ({ streetAddressTable: { @@ -1083,6 +1602,23 @@ test('streetAddress unique generator test', async () => { expect(predicate).toBe(true); }); +test('streetAddress array generator test', async () => { + await seed(db, { streetAddressTable: schema.streetAddressArrayTable }).refine((funcs) => ({ + streetAddressTable: { + count, + columns: { + streetAddress: funcs.streetAddress({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.streetAddressArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('jobTitle generator test', async () => { await seed(db, { jobTitleTable: schema.jobTitleTable }).refine((funcs) => ({ jobTitleTable: { @@ -1100,6 +1636,23 @@ test('jobTitle generator test', async () => { expect(predicate).toBe(true); }); +test('jobTitle array generator test', async () => { + await seed(db, { jobTitleTable: schema.jobTitleArrayTable }).refine((funcs) => ({ + jobTitleTable: { + count, + columns: { + jobTitle: funcs.jobTitle({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.jobTitleArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('postcode generator test', async () => { await seed(db, { postcodeTable: schema.postcodeTable }).refine((funcs) => ({ postcodeTable: { @@ -1134,6 +1687,23 @@ test('postcode unique generator test', async () => { expect(predicate).toBe(true); }); +test('postcode array generator test', async () => { + await seed(db, { postcodeTable: schema.postcodeArrayTable }).refine((funcs) => ({ + postcodeTable: { + count, + columns: { + postcode: funcs.postcode({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.postcodeArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('state generator test', async () => { await seed(db, { stateTable: schema.stateTable }).refine((funcs) => ({ stateTable: { @@ -1151,6 +1721,23 @@ test('state generator test', async () => { expect(predicate).toBe(true); }); +test('state array generator test', async () => { + await seed(db, { stateTable: schema.stateArrayTable }).refine((funcs) => ({ + stateTable: { + count, + columns: { + state: funcs.state({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.stateArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('companyName generator test', async () => { await seed(db, { companyNameTable: schema.companyNameTable }).refine((funcs) => ({ companyNameTable: { @@ -1185,6 +1772,23 @@ test('companyName unique generator test', async () => { expect(predicate).toBe(true); }); +test('companyName array generator test', async () => { + await seed(db, { companyNameTable: schema.companyNameArrayTable }).refine((funcs) => ({ + companyNameTable: { + count, + columns: { + companyName: funcs.companyName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.companyNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('loremIpsum generator test', async () => { await seed(db, { loremIpsumTable: schema.loremIpsumTable }).refine((funcs) => ({ loremIpsumTable: { @@ -1202,6 +1806,23 @@ test('loremIpsum generator test', async () => { expect(predicate).toBe(true); }); +test('loremIpsum array generator test', async () => { + await seed(db, { loremIpsumTable: schema.loremIpsumArrayTable }).refine((funcs) => ({ + loremIpsumTable: { + count, + columns: { + loremIpsum: funcs.loremIpsum({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.loremIpsumArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('point generator test', async () => { await seed(db, { pointTable: schema.pointTable }).refine((funcs) => ({ pointTable: { @@ -1242,6 +1863,23 @@ test('point unique generator test', async () => { expect(predicate).toBe(true); }); +test('point array generator test', async () => { + await seed(db, { pointTable: schema.pointArrayTable }).refine((funcs) => ({ + pointTable: { + count, + columns: { + point: funcs.point({ arraySize: 2 }), + }, + }, + })); + + const data = await db.select().from(schema.pointArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 2)); + expect(predicate).toBe(true); +}); + test('line generator test', async () => { await seed(db, { lineTable: schema.lineTable }).refine((funcs) => ({ lineTable: { @@ -1282,6 +1920,23 @@ test('line unique generator test', async () => { expect(predicate).toBe(true); }); +test('line array generator test', async () => { + await seed(db, { lineTable: schema.lineArrayTable }).refine((funcs) => ({ + lineTable: { + count, + columns: { + line: funcs.line({ arraySize: 2 }), + }, + }, + })); + + const data = await db.select().from(schema.lineArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 2)); + expect(predicate).toBe(true); +}); + test('phoneNumber generator test', async () => { await seed(db, { phoneNumberTable: schema.phoneNumberTable }).refine((funcs) => ({ phoneNumberTable: { @@ -1304,6 +1959,34 @@ test('phoneNumber generator test', async () => { expect(predicate).toBe(true); }); +test('phoneNumber array generator test', async () => { + await seed(db, { phoneNumberTable: schema.phoneNumberArrayTable }).refine((funcs) => ({ + phoneNumberTable: { + count, + columns: { + phoneNumber: funcs.phoneNumber({ arraySize: 3 }), + phoneNumberPrefixes: funcs.phoneNumber({ + prefixes: ['+380 99', '+380 67', '+1'], + generatedDigitsNumbers: [7, 7, 10], + arraySize: 4, + }), + phoneNumberTemplate: funcs.phoneNumber({ + template: '+380 ## ## ### ##', + arraySize: 5, + }), + }, + }, + })); + + const data = await db.select().from(schema.phoneNumberArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && [3, 4, 5].includes(val.length)) + ); + expect(predicate).toBe(true); +}); + test('weightedRandom generator test', async () => { await seed(db, { weightedRandomTable: schema.weightedRandomTable }).refine((funcs) => ({ weightedRandomTable: { @@ -1372,3 +2055,43 @@ test('weightedRandom with unique gens generator test', async () => { 'The weights for the Weighted Random feature must add up to exactly 1. Please review your weights to ensure they total 1 before proceeding', ); }); + +test('uuid generator test', async () => { + await reset(db, { uuidTable: schema.uuidTable }); + await seed(db, { uuidTable: schema.uuidTable }).refine((funcs) => ({ + uuidTable: { + count, + columns: { + uuid: funcs.uuid(), + }, + }, + })); + + const data = await db.select().from(schema.uuidTable); + // every value in each row does not equal undefined. + let predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + const uuidStrsSet = new Set(data.map((row) => row.uuid!)); + predicate = uuidStrsSet.size === data.length; + expect(predicate).toBe(true); +}); + +test('uuid array generator test', async () => { + await reset(db, { uuidArrayTable: schema.uuidArrayTable }); + await seed(db, { uuidArrayTable: schema.uuidArrayTable }).refine((funcs) => ({ + uuidArrayTable: { + count, + columns: { + uuid: funcs.uuid({ arraySize: 4 }), + }, + }, + })); + + const data = await db.select().from(schema.uuidArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/src/tests/pg/generatorsTest/pgSchema.ts b/drizzle-seed/tests/pg/generatorsTest/pgSchema.ts similarity index 63% rename from drizzle-seed/src/tests/pg/generatorsTest/pgSchema.ts rename to drizzle-seed/tests/pg/generatorsTest/pgSchema.ts index 57f585297..48902ac6e 100644 --- a/drizzle-seed/src/tests/pg/generatorsTest/pgSchema.ts +++ b/drizzle-seed/tests/pg/generatorsTest/pgSchema.ts @@ -11,6 +11,7 @@ import { text, time, timestamp, + uuid, varchar, } from 'drizzle-orm/pg-core'; @@ -26,6 +27,10 @@ export const defaultTable = schema.table('default_table', { defaultString: text('default_string'), }); +export const defaultArrayTable = schema.table('default_array_table', { + defaultString: text('default_string').array(), +}); + export const valuesFromArrayTable = schema.table('values_from_array_table', { valuesFromArrayNotNull: varchar('values_from_array_not_null', { length: 256 }).notNull(), valuesFromArrayWeightedNotNull: varchar('values_from_array_weighted_not_null', { length: 256 }).notNull(), @@ -38,6 +43,10 @@ export const valuesFromArrayUniqueTable = schema.table('values_from_array_unique valuesFromArrayWeightedNotNull: varchar('values_from_array_weighted_not_null', { length: 256 }).unique().notNull(), }); +export const valuesFromArrayArrayTable = schema.table('values_from_array_array_table', { + valuesFromArray: varchar('values_from_array', { length: 256 }).array(), +}); + export const intPrimaryKeyTable = schema.table('int_primary_key_table', { intPrimaryKey: integer('int_primary_key').unique(), }); @@ -50,6 +59,10 @@ export const numberUniqueTable = schema.table('number_unique_table', { numberUnique: real('number_unique').unique(), }); +export const numberArrayTable = schema.table('number_array_table', { + number: real('number').array(), +}); + export const intTable = schema.table('int_table', { int: integer('int'), }); @@ -58,26 +71,52 @@ export const intUniqueTable = schema.table('int_unique_table', { intUnique: integer('int_unique').unique(), }); +export const intArrayTable = schema.table('int_array_table', { + int: integer('int').array(), +}); + export const booleanTable = schema.table('boolean_table', { boolean: boolean('boolean'), }); +export const booleanArrayTable = schema.table('boolean_array_table', { + boolean: boolean('boolean').array(), +}); + export const dateTable = schema.table('date_table', { date: date('date'), }); +// TODO: add tests for data type with different modes +export const dateArrayTable = schema.table('date_array_table', { + date: date('date', { mode: 'date' }).array(), + dateString: date('date_string', { mode: 'string' }).array(), +}); + export const timeTable = schema.table('time_table', { time: time('time'), }); +export const timeArrayTable = schema.table('time_array_table', { + time: time('time').array(), +}); + export const timestampTable = schema.table('timestamp_table', { timestamp: timestamp('timestamp'), }); +export const timestampArrayTable = schema.table('timestamp_array_table', { + timestamp: timestamp('timestamp').array(), +}); + export const jsonTable = schema.table('json_table', { json: json('json'), }); +export const jsonArrayTable = schema.table('json_array_table', { + json: json('json').array(), +}); + export const intervalTable = schema.table('interval_table', { interval: interval('interval'), }); @@ -86,6 +125,10 @@ export const intervalUniqueTable = schema.table('interval_unique_table', { intervalUnique: interval('interval_unique').unique(), }); +export const intervalArrayTable = schema.table('interval_array_table', { + interval: interval('interval').array(), +}); + export const stringTable = schema.table('string_table', { string: text('string'), }); @@ -94,10 +137,18 @@ export const stringUniqueTable = schema.table('string_unique_table', { stringUnique: varchar('string_unique', { length: 256 }).unique(), }); +export const stringArrayTable = schema.table('string_array_table', { + string: text('string').array(), +}); + export const emailTable = schema.table('email_table', { email: varchar('email', { length: 256 }).unique(), }); +export const emailArrayTable = schema.table('email_array_table', { + email: varchar('email', { length: 256 }).array(), +}); + export const firstNameTable = schema.table('first_name_table', { firstName: varchar('first_name', { length: 256 }), }); @@ -106,6 +157,10 @@ export const firstNameUniqueTable = schema.table('first_name_unique_table', { firstNameUnique: varchar('first_name_unique', { length: 256 }).unique(), }); +export const firstNameArrayTable = schema.table('first_name_array_table', { + firstName: varchar('first_name', { length: 256 }).array(), +}); + export const lastNameTable = schema.table('last_name_table', { lastName: varchar('last_name', { length: 256 }), }); @@ -114,6 +169,10 @@ export const lastNameUniqueTable = schema.table('last_name_unique_table', { lastNameUnique: varchar('last_name_unique', { length: 256 }).unique(), }); +export const lastNameArrayTable = schema.table('last_name_array_table', { + lastName: varchar('last_name', { length: 256 }).array(), +}); + export const fullNameTable = schema.table('full_name__table', { fullName: varchar('full_name_', { length: 256 }), }); @@ -122,6 +181,10 @@ export const fullNameUniqueTable = schema.table('full_name_unique_table', { fullNameUnique: varchar('full_name_unique', { length: 256 }).unique(), }); +export const fullNameArrayTable = schema.table('full_name_array_table', { + fullName: varchar('full_name', { length: 256 }).array(), +}); + export const countryTable = schema.table('country_table', { country: varchar('country', { length: 256 }), }); @@ -130,6 +193,10 @@ export const countryUniqueTable = schema.table('country_unique_table', { countryUnique: varchar('country_unique', { length: 256 }).unique(), }); +export const countryArrayTable = schema.table('country_array_table', { + country: varchar('country', { length: 256 }).array(), +}); + export const cityTable = schema.table('city_table', { city: varchar('city', { length: 256 }), }); @@ -138,6 +205,10 @@ export const cityUniqueTable = schema.table('city_unique_table', { cityUnique: varchar('city_unique', { length: 256 }).unique(), }); +export const cityArrayTable = schema.table('city_array_table', { + city: varchar('city', { length: 256 }).array(), +}); + export const streetAddressTable = schema.table('street_address_table', { streetAddress: varchar('street_address', { length: 256 }), }); @@ -146,10 +217,18 @@ export const streetAddressUniqueTable = schema.table('street_address_unique_tabl streetAddressUnique: varchar('street_address_unique', { length: 256 }).unique(), }); +export const streetAddressArrayTable = schema.table('street_address_array_table', { + streetAddress: varchar('street_address', { length: 256 }).array(), +}); + export const jobTitleTable = schema.table('job_Title_table', { jobTitle: text('job_title'), }); +export const jobTitleArrayTable = schema.table('job_title_array_table', { + jobTitle: text('job_title').array(), +}); + export const postcodeTable = schema.table('postcode_table', { postcode: varchar('postcode', { length: 256 }), }); @@ -158,10 +237,18 @@ export const postcodeUniqueTable = schema.table('postcode_unique_table', { postcodeUnique: varchar('postcode_unique', { length: 256 }).unique(), }); +export const postcodeArrayTable = schema.table('postcode_array_table', { + postcode: varchar('postcode', { length: 256 }).array(), +}); + export const stateTable = schema.table('state_table', { state: text('state'), }); +export const stateArrayTable = schema.table('state_array_table', { + state: text('state').array(), +}); + export const companyNameTable = schema.table('company_name_table', { companyName: text('company_name'), }); @@ -170,18 +257,34 @@ export const companyNameUniqueTable = schema.table('company_name_unique_table', companyNameUnique: varchar('company_name_unique', { length: 256 }).unique(), }); +export const companyNameArrayTable = schema.table('company_name_array_table', { + companyName: text('company_name').array(), +}); + export const loremIpsumTable = schema.table('lorem_ipsum_table', { loremIpsum: text('lorem_ipsum'), }); +export const loremIpsumArrayTable = schema.table('lorem_ipsum_array_table', { + loremIpsum: text('lorem_ipsum').array(), +}); + export const pointTable = schema.table('point_table', { point: point('point'), }); +export const pointArrayTable = schema.table('point_array_table', { + point: point('point').array(), +}); + export const lineTable = schema.table('line_table', { line: line('line'), }); +export const lineArrayTable = schema.table('line_array_table', { + line: line('line').array(), +}); + // export const pointUniqueTable = schema.table("point_unique_table", { // pointUnique: point("point_unique").unique(), // }); @@ -196,6 +299,12 @@ export const phoneNumberTable = schema.table('phone_number_table', { phoneNumberPrefixes: varchar('phone_number_prefixes', { length: 256 }).unique(), }); +export const phoneNumberArrayTable = schema.table('phone_number_array_table', { + phoneNumber: varchar('phoneNumber', { length: 256 }).array(), + phoneNumberTemplate: varchar('phone_number_template', { length: 256 }).array(), + phoneNumberPrefixes: varchar('phone_number_prefixes', { length: 256 }).array(), +}); + export const weightedRandomTable = schema.table('weighted_random_table', { weightedRandom: varchar('weighted_random', { length: 256 }), }); @@ -203,3 +312,11 @@ export const weightedRandomTable = schema.table('weighted_random_table', { export const weightedRandomWithUniqueGensTable = schema.table('weighted_random_with_unique_gens_table', { weightedRandomWithUniqueGens: varchar('weighted_random_with_unique_gens', { length: 256 }).unique(), }); + +export const uuidTable = schema.table('uuid_table', { + uuid: uuid('uuid'), +}); + +export const uuidArrayTable = schema.table('uuid_array_table', { + uuid: uuid('uuid').array(), +}); diff --git a/drizzle-seed/src/tests/pg/pg.test.ts b/drizzle-seed/tests/pg/pg.test.ts similarity index 91% rename from drizzle-seed/src/tests/pg/pg.test.ts rename to drizzle-seed/tests/pg/pg.test.ts index bd9ec7504..90d6b4fc2 100644 --- a/drizzle-seed/src/tests/pg/pg.test.ts +++ b/drizzle-seed/tests/pg/pg.test.ts @@ -3,7 +3,7 @@ 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 '../../index.ts'; +import { reset, seed } from '../../src/index.ts'; import * as schema from './pgSchema.ts'; let client: PGlite; @@ -190,6 +190,20 @@ beforeAll(async () => { ); `, ); + + await db.execute( + sql` + create table "seeder_lib_pg"."user" + ( + id serial + primary key, + name text, + "invitedBy" integer + constraint "user_invitedBy_user_id_fk" + references "seeder_lib_pg"."user" + ); + `, + ); }); afterEach(async () => { @@ -324,7 +338,15 @@ test("redefine(refine) orders count using 'with' in customers", async () => { }); test("sequential using of 'with'", async () => { - await seed(db, schema, { count: 11 }).refine(() => ({ + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema, { count: 11 }).refine(() => ({ customers: { count: 4, with: { @@ -361,3 +383,13 @@ test('seeding with identity columns', async () => { expect(result.length).toBe(10); }); + +test('seeding with self relation', async () => { + await seed(db, { user: schema.user }); + + const result = await db.select().from(schema.user); + + expect(result.length).toBe(10); + const predicate = result.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/src/tests/pg/pgSchema.ts b/drizzle-seed/tests/pg/pgSchema.ts similarity index 94% rename from drizzle-seed/src/tests/pg/pgSchema.ts rename to drizzle-seed/tests/pg/pgSchema.ts index 2c1c95045..f6f4f8347 100644 --- a/drizzle-seed/src/tests/pg/pgSchema.ts +++ b/drizzle-seed/tests/pg/pgSchema.ts @@ -25,7 +25,7 @@ // }); import type { AnyPgColumn } from 'drizzle-orm/pg-core'; -import { integer, numeric, pgSchema, text, timestamp, varchar } from 'drizzle-orm/pg-core'; +import { integer, numeric, pgSchema, serial, text, timestamp, varchar } from 'drizzle-orm/pg-core'; export const schema = pgSchema('seeder_lib_pg'); @@ -134,3 +134,12 @@ export const identityColumnsTable = schema.table('identity_columns_table', { id1: integer().generatedByDefaultAsIdentity(), name: text(), }); + +export const user = schema.table( + 'user', + { + id: serial().primaryKey(), + name: text(), + invitedBy: integer().references((): AnyPgColumn => user.id), + }, +); diff --git a/drizzle-seed/src/tests/sqlite/allDataTypesTest/sqliteSchema.ts b/drizzle-seed/tests/sqlite/allDataTypesTest/sqliteSchema.ts similarity index 100% rename from drizzle-seed/src/tests/sqlite/allDataTypesTest/sqliteSchema.ts rename to drizzle-seed/tests/sqlite/allDataTypesTest/sqliteSchema.ts diff --git a/drizzle-seed/src/tests/sqlite/allDataTypesTest/sqlite_all_data_types.test.ts b/drizzle-seed/tests/sqlite/allDataTypesTest/sqlite_all_data_types.test.ts similarity index 96% rename from drizzle-seed/src/tests/sqlite/allDataTypesTest/sqlite_all_data_types.test.ts rename to drizzle-seed/tests/sqlite/allDataTypesTest/sqlite_all_data_types.test.ts index b377ba9a1..8282f921d 100644 --- a/drizzle-seed/src/tests/sqlite/allDataTypesTest/sqlite_all_data_types.test.ts +++ b/drizzle-seed/tests/sqlite/allDataTypesTest/sqlite_all_data_types.test.ts @@ -3,7 +3,7 @@ import { sql } from 'drizzle-orm'; import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; import { drizzle } from 'drizzle-orm/better-sqlite3'; import { afterAll, beforeAll, expect, test } from 'vitest'; -import { seed } from '../../../index.ts'; +import { seed } from '../../../src/index.ts'; import * as schema from './sqliteSchema.ts'; let client: BetterSqlite3.Database; diff --git a/drizzle-seed/tests/sqlite/cyclicTables/cyclicTables.test.ts b/drizzle-seed/tests/sqlite/cyclicTables/cyclicTables.test.ts new file mode 100644 index 000000000..d404072eb --- /dev/null +++ b/drizzle-seed/tests/sqlite/cyclicTables/cyclicTables.test.ts @@ -0,0 +1,138 @@ +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` + create table model + ( + id integer not null + primary key, + name text not null, + defaultImageId integer, + foreign key (defaultImageId) references model_image + ); + `, + ); + + db.run( + sql` + create table model_image + ( + id integer not null + primary key, + url text not null, + caption text, + modelId integer not null + references model + ); + `, + ); + + // 3 tables case + db.run( + sql` + create table model1 + ( + id integer not null + primary key, + name text not null, + userId integer, + defaultImageId integer, + foreign key (defaultImageId) references model_image1, + foreign key (userId) references user + ); + `, + ); + + db.run( + sql` + create table model_image1 + ( + id integer not null + primary key, + url text not null, + caption text, + modelId integer not null + references model1 + ); + `, + ); + + db.run( + sql` + create table user + ( + id integer not null + primary key, + name text, + invitedBy integer + references user, + imageId integer not null + references model_image1 + ); + `, + ); +}); + +afterEach(async () => { + await reset(db, schema); +}); + +afterAll(async () => { + client.close(); +}); + +test('2 cyclic tables test', async () => { + await seed(db, { + modelTable: schema.modelTable, + modelImageTable: schema.modelImageTable, + }); + + const modelTable = await db.select().from(schema.modelTable); + const modelImageTable = await db.select().from(schema.modelImageTable); + + expect(modelTable.length).toBe(10); + let predicate = modelTable.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(modelImageTable.length).toBe(10); + predicate = modelImageTable.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); + +test('3 cyclic tables test', async () => { + await seed(db, { + modelTable1: schema.modelTable1, + modelImageTable1: schema.modelImageTable1, + user: schema.user, + }); + + const modelTable1 = await db.select().from(schema.modelTable1); + const modelImageTable1 = await db.select().from(schema.modelImageTable1); + const user = await db.select().from(schema.user); + + expect(modelTable1.length).toBe(10); + let predicate = modelTable1.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(modelImageTable1.length).toBe(10); + predicate = modelImageTable1.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(user.length).toBe(10); + predicate = user.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/sqlite/cyclicTables/sqliteSchema.ts b/drizzle-seed/tests/sqlite/cyclicTables/sqliteSchema.ts new file mode 100644 index 000000000..c9babadde --- /dev/null +++ b/drizzle-seed/tests/sqlite/cyclicTables/sqliteSchema.ts @@ -0,0 +1,76 @@ +import { relations } from 'drizzle-orm'; +import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import type { AnySQLiteColumn } from 'drizzle-orm/sqlite-core'; + +// MODEL +export const modelTable = sqliteTable( + 'model', + { + id: integer().primaryKey(), + name: text().notNull(), + defaultImageId: integer().references(() => modelImageTable.id), + }, +); + +export const modelRelations = relations(modelTable, ({ one, many }) => ({ + images: many(modelImageTable), + defaultImage: one(modelImageTable, { + fields: [modelTable.defaultImageId], + references: [modelImageTable.id], + }), +})); + +// MODEL IMAGE +export const modelImageTable = sqliteTable( + 'model_image', + { + id: integer().primaryKey(), + url: text().notNull(), + caption: text(), + modelId: integer() + .notNull() + .references((): AnySQLiteColumn => modelTable.id), + }, +); + +export const modelImageRelations = relations(modelImageTable, ({ one }) => ({ + model: one(modelTable, { + fields: [modelImageTable.modelId], + references: [modelTable.id], + }), +})); + +// 3 tables case +export const modelTable1 = sqliteTable( + 'model1', + { + id: integer().primaryKey(), + name: text().notNull(), + userId: integer() + .references(() => user.id), + defaultImageId: integer().references(() => modelImageTable1.id), + }, +); + +export const modelImageTable1 = sqliteTable( + 'model_image1', + { + id: integer().primaryKey(), + url: text().notNull(), + caption: text(), + modelId: integer().notNull() + .references((): AnySQLiteColumn => modelTable1.id), + }, +); + +export const user = sqliteTable( + 'user', + { + id: integer().primaryKey(), + name: text(), + invitedBy: integer().references((): AnySQLiteColumn => user.id), + imageId: integer() + .notNull() + .references((): AnySQLiteColumn => modelImageTable1.id), + }, +); diff --git a/drizzle-seed/src/tests/sqlite/sqlite.test.ts b/drizzle-seed/tests/sqlite/sqlite.test.ts similarity index 99% rename from drizzle-seed/src/tests/sqlite/sqlite.test.ts rename to drizzle-seed/tests/sqlite/sqlite.test.ts index 50db1fa8b..550648d49 100644 --- a/drizzle-seed/src/tests/sqlite/sqlite.test.ts +++ b/drizzle-seed/tests/sqlite/sqlite.test.ts @@ -3,7 +3,7 @@ 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 '../../index.ts'; +import { reset, seed } from '../../src/index.ts'; import * as schema from './sqliteSchema.ts'; let client: BetterSqlite3.Database; diff --git a/drizzle-seed/src/tests/sqlite/sqliteSchema.ts b/drizzle-seed/tests/sqlite/sqliteSchema.ts similarity index 100% rename from drizzle-seed/src/tests/sqlite/sqliteSchema.ts rename to drizzle-seed/tests/sqlite/sqliteSchema.ts diff --git a/drizzle-seed/tsconfig.json b/drizzle-seed/tsconfig.json index c75766c3f..222e6a4dd 100644 --- a/drizzle-seed/tsconfig.json +++ b/drizzle-seed/tsconfig.json @@ -43,6 +43,6 @@ "~/*": ["src/*"] } }, - "exclude": ["**/dist"], - "include": ["src", "*.ts"] + "exclude": ["**/dist", "src/schemaTest.ts", "src/test.ts"], + "include": ["src", "*.ts", "tests"] } diff --git a/drizzle-seed/src/type-tests/mysql.ts b/drizzle-seed/type-tests/mysql.ts similarity index 91% rename from drizzle-seed/src/type-tests/mysql.ts rename to drizzle-seed/type-tests/mysql.ts index d1c0949b3..ffd42726d 100644 --- a/drizzle-seed/src/type-tests/mysql.ts +++ b/drizzle-seed/type-tests/mysql.ts @@ -1,7 +1,7 @@ import type { MySqlColumn } from 'drizzle-orm/mysql-core'; import { int, mysqlTable, text } from 'drizzle-orm/mysql-core'; import { drizzle as mysql2Drizzle } from 'drizzle-orm/mysql2'; -import { reset, seed } from '../index.ts'; +import { reset, seed } from '../src/index.ts'; const mysqlUsers = mysqlTable('users', { id: int().primaryKey().autoincrement(), diff --git a/drizzle-seed/src/type-tests/pg.ts b/drizzle-seed/type-tests/pg.ts similarity index 96% rename from drizzle-seed/src/type-tests/pg.ts rename to drizzle-seed/type-tests/pg.ts index 68faf844b..3bec9989f 100644 --- a/drizzle-seed/src/type-tests/pg.ts +++ b/drizzle-seed/type-tests/pg.ts @@ -3,7 +3,7 @@ import type { PgColumn } from 'drizzle-orm/pg-core'; import { integer, pgTable, text } from 'drizzle-orm/pg-core'; import { drizzle as pgliteDrizzle } from 'drizzle-orm/pglite'; import { drizzle as postgresJsDrizzle } from 'drizzle-orm/postgres-js'; -import { reset, seed } from '../index.ts'; +import { reset, seed } from '../src/index.ts'; const pgUsers = pgTable('users', { id: integer().primaryKey().generatedAlwaysAsIdentity(), diff --git a/drizzle-seed/src/type-tests/sqlite.ts b/drizzle-seed/type-tests/sqlite.ts similarity index 91% rename from drizzle-seed/src/type-tests/sqlite.ts rename to drizzle-seed/type-tests/sqlite.ts index 1b060e6a3..c9fa3d23b 100644 --- a/drizzle-seed/src/type-tests/sqlite.ts +++ b/drizzle-seed/type-tests/sqlite.ts @@ -1,7 +1,7 @@ import { drizzle as betterSqlite3Drizzle } from 'drizzle-orm/better-sqlite3'; import type { SQLiteColumn } from 'drizzle-orm/sqlite-core'; import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; -import { reset, seed } from '../index.ts'; +import { reset, seed } from '../src/index.ts'; const mysqlUsers = sqliteTable('users', { id: int().primaryKey(), diff --git a/drizzle-seed/src/type-tests/tsconfig.json b/drizzle-seed/type-tests/tsconfig.json similarity index 80% rename from drizzle-seed/src/type-tests/tsconfig.json rename to drizzle-seed/type-tests/tsconfig.json index 09df8e1bb..b4e6c8007 100644 --- a/drizzle-seed/src/type-tests/tsconfig.json +++ b/drizzle-seed/type-tests/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.build.json", + "extends": "../tsconfig.build.json", "compilerOptions": { "composite": false, "noEmit": true, diff --git a/drizzle-seed/src/tests/vitest.config.ts b/drizzle-seed/vitest.config.ts similarity index 76% rename from drizzle-seed/src/tests/vitest.config.ts rename to drizzle-seed/vitest.config.ts index a74ccd37c..5489010bd 100644 --- a/drizzle-seed/src/tests/vitest.config.ts +++ b/drizzle-seed/vitest.config.ts @@ -3,9 +3,9 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { include: [ - './src/tests/pg/**/*.test.ts', - './src/tests/mysql/**/*.test.ts', - './src/tests/sqlite/**/*.test.ts', + './tests/pg/**/*.test.ts', + './tests/mysql/**/*.test.ts', + './tests/sqlite/**/*.test.ts', ], exclude: [], typecheck: { diff --git a/drizzle-typebox/package.json b/drizzle-typebox/package.json index 25461ad15..a6c34fc69 100644 --- a/drizzle-typebox/package.json +++ b/drizzle-typebox/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-typebox", - "version": "0.1.1", + "version": "0.2.0", "description": "Generate Typebox schemas from Drizzle ORM schemas", "type": "module", "scripts": { diff --git a/drizzle-valibot/package.json b/drizzle-valibot/package.json index 9d14c39b9..c9e6a02e9 100644 --- a/drizzle-valibot/package.json +++ b/drizzle-valibot/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-valibot", - "version": "0.2.0", + "version": "0.3.0", "description": "Generate valibot schemas from Drizzle ORM schemas", "type": "module", "scripts": { diff --git a/drizzle-zod/package.json b/drizzle-zod/package.json index d4ed4407c..ebbec398e 100644 --- a/drizzle-zod/package.json +++ b/drizzle-zod/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-zod", - "version": "0.5.1", + "version": "0.6.0", "description": "Generate Zod schemas from Drizzle ORM schemas", "type": "module", "scripts": { diff --git a/integration-tests/tests/mysql/mysql-common.ts b/integration-tests/tests/mysql/mysql-common.ts index a2a0baeb0..521e70407 100644 --- a/integration-tests/tests/mysql/mysql-common.ts +++ b/integration-tests/tests/mysql/mysql-common.ts @@ -37,6 +37,7 @@ import { foreignKey, getTableConfig, getViewConfig, + index, int, intersect, intersectAll, @@ -3888,6 +3889,37 @@ export function tests(driver?: string) { expect(users.length).toBeGreaterThan(0); }); + test('define constraints as array', async (ctx) => { + const { db } = ctx.mysql; + + const table = mysqlTable('name', { + id: int(), + }, (t) => [ + index('name').on(t.id), + primaryKey({ columns: [t.id], name: 'custom' }), + ]); + + const { indexes, primaryKeys } = getTableConfig(table); + + expect(indexes.length).toBe(1); + expect(primaryKeys.length).toBe(1); + }); + + test('define constraints as array inside third param', async (ctx) => { + const { db } = ctx.mysql; + + const table = mysqlTable('name', { + id: int(), + }, (t) => [ + [index('name').on(t.id), primaryKey({ columns: [t.id], name: 'custom' })], + ]); + + const { indexes, primaryKeys } = getTableConfig(table); + + expect(indexes.length).toBe(1); + expect(primaryKeys.length).toBe(1); + }); + test('update with limit and order by', async (ctx) => { const { db } = ctx.mysql; diff --git a/integration-tests/tests/seeder/mysql.test.ts b/integration-tests/tests/seeder/mysql.test.ts index 5ae7f9f15..22530a2aa 100644 --- a/integration-tests/tests/seeder/mysql.test.ts +++ b/integration-tests/tests/seeder/mysql.test.ts @@ -442,7 +442,7 @@ test("sequential using of 'with'", async () => { // All data types test ------------------------------- test('basic seed test for all mysql data types', async () => { - await seed(db, schema, { count: 10000 }); + await seed(db, schema, { count: 1000 }); const allDataTypes = await db.select().from(schema.allDataTypes); diff --git a/integration-tests/tests/seeder/pg.test.ts b/integration-tests/tests/seeder/pg.test.ts index 3ca75704a..48109f9fa 100644 --- a/integration-tests/tests/seeder/pg.test.ts +++ b/integration-tests/tests/seeder/pg.test.ts @@ -2,7 +2,7 @@ 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 { firstNames, lastNames, reset, seed } from 'drizzle-seed'; +import { cities, countries, firstNames, lastNames, reset, seed } from 'drizzle-seed'; import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; import * as schema from './pgSchema.ts'; @@ -195,10 +195,10 @@ const createAllDataTypesTable = async () => { "smallint" smallint, "bigint" bigint, "bigint_number" bigint, - "serial" serial NOT NULL, - "smallserial" "smallserial" NOT NULL, + "serial" serial, + "smallserial" smallserial, "bigserial" bigserial, - "bigserial_number" bigserial NOT NULL, + "bigserial_number" bigserial, "boolean" boolean, "text" text, "varchar" varchar(256), @@ -219,7 +219,51 @@ const createAllDataTypesTable = async () => { "point_tuple" "point", "line" "line", "line_tuple" "line", - "mood_enum" "seeder_lib_pg"."mood_enum" + "mood_enum" "seeder_lib_pg"."mood_enum", + "uuid" "uuid" + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."all_array_data_types" ( + "integer_array" integer[], + "smallint_array" smallint[], + "bigint_array" bigint[], + "bigint_number_array" bigint[], + "boolean_array" boolean[], + "text_array" text[], + "varchar_array" varchar(256)[], + "char_array" char(256)[], + "numeric_array" numeric[], + "decimal_array" numeric[], + "real_array" real[], + "double_precision_array" double precision[], + "json_array" json[], + "jsonb_array" jsonb[], + "time_array" time[], + "timestamp_date_array" timestamp[], + "timestamp_string_array" timestamp[], + "date_string_array" date[], + "date_array" date[], + "interval_array" interval[], + "point_array" "point"[], + "point_tuple_array" "point"[], + "line_array" "line"[], + "line_tuple_array" "line"[], + "mood_enum_array" "seeder_lib_pg"."mood_enum"[] + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."nd_arrays" ( + "integer_1d_array" integer[3], + "integer_2d_array" integer[3][4], + "integer_3d_array" integer[3][4][5], + "integer_4d_array" integer[3][4][5][6] ); `, ); @@ -235,6 +279,21 @@ const createAllGeneratorsTables = async () => { END $$; `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."default_table" ( + "default_string" text + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."default_array_table" ( + "default_string" text[] + ); + `, + ); await db.execute( sql` @@ -244,6 +303,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."boolean_array_table" ( + "boolean" boolean[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."city_table" ( @@ -261,6 +328,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."city_array_table" ( + "city" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."company_name_table" ( @@ -278,6 +353,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."company_name_array_table" ( + "company_name" text[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."country_table" ( @@ -295,6 +378,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."country_array_table" ( + "country" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."date_table" ( @@ -305,8 +396,9 @@ const createAllGeneratorsTables = async () => { await db.execute( sql` - CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."default_table" ( - "default_string" text + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."date_array_table" ( + "date" date[], + "date_string" date[] ); `, ); @@ -320,6 +412,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."email_array_table" ( + "email" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."enum_table" ( @@ -345,6 +445,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."first_name_array_table" ( + "first_name" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."full_name__table" ( @@ -362,6 +470,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."full_name_array_table" ( + "full_name" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."int_primary_key_table" ( @@ -388,6 +504,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."int_array_table" ( + "int" integer[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."interval_table" ( @@ -405,6 +529,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."interval_array_table" ( + "interval" interval[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."job_title_table" ( @@ -413,6 +545,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."job_title_array_table" ( + "job_title" text[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."json_table" ( @@ -421,6 +561,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."json_array_table" ( + "json" json[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."last_name_table" ( @@ -438,6 +586,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."last_name_array_table" ( + "last_name" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."line_table" ( @@ -446,6 +602,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."line_array_table" ( + "line" "line"[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."lorem_ipsum_table" ( @@ -454,6 +618,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."lorem_ipsum_array_table" ( + "lorem_ipsum" text[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."number_table" ( @@ -471,6 +643,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."number_array_table" ( + "number" real[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."phone_number_table" ( @@ -484,6 +664,16 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."phone_number_array_table" ( + "phoneNumber" varchar(256)[], + "phone_number_template" varchar(256)[], + "phone_number_prefixes" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."point_table" ( @@ -492,6 +682,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."point_array_table" ( + "point" "point"[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."postcode_table" ( @@ -509,6 +707,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."postcode_array_table" ( + "postcode" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."state_table" ( @@ -517,6 +723,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."state_array_table" ( + "state" text[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."street_address_table" ( @@ -534,6 +748,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."street_address_array_table" ( + "street_address" varchar(256)[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."string_table" ( @@ -551,6 +773,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."string_array_table" ( + "string" text[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."time_table" ( @@ -559,6 +789,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."time_array_table" ( + "time" time[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."timestamp_table" ( @@ -567,6 +805,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."timestamp_array_table" ( + "timestamp" timestamp[] + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."values_from_array_table" ( @@ -591,6 +837,14 @@ const createAllGeneratorsTables = async () => { `, ); + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."values_from_array_array_table" ( + "values_from_array" varchar(256) + ); + `, + ); + await db.execute( sql` CREATE TABLE IF NOT EXISTS "seeder_lib_pg"."weighted_random_table" ( @@ -640,7 +894,15 @@ afterAll(async () => { }); test('basic seed test', async () => { - await seed(db, schema); + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema); const customers = await db.select().from(schema.customers); const details = await db.select().from(schema.details); @@ -658,7 +920,15 @@ test('basic seed test', async () => { }); test('seed with options.count:11 test', async () => { - await seed(db, schema, { count: 11 }); + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema, { count: 11 }); const customers = await db.select().from(schema.customers); const details = await db.select().from(schema.details); @@ -676,7 +946,15 @@ test('seed with options.count:11 test', async () => { }); test('redefine(refine) customers count', async () => { - await seed(db, schema, { count: 11 }).refine(() => ({ + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema, { count: 11 }).refine(() => ({ customers: { count: 12, }, @@ -698,7 +976,15 @@ test('redefine(refine) customers count', async () => { }); test('redefine(refine) all tables count', async () => { - await seed(db, schema, { count: 11 }).refine(() => ({ + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema, { count: 11 }).refine(() => ({ customers: { count: 12, }, @@ -735,7 +1021,15 @@ test('redefine(refine) all tables count', async () => { }); test("redefine(refine) orders count using 'with' in customers", async () => { - await seed(db, schema, { count: 11 }).refine(() => ({ + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema, { count: 11 }).refine(() => ({ customers: { count: 4, with: { @@ -763,7 +1057,15 @@ test("redefine(refine) orders count using 'with' in customers", async () => { }); test("sequential using of 'with'", async () => { - await seed(db, schema, { count: 11 }).refine(() => ({ + const currSchema = { + customers: schema.customers, + details: schema.details, + employees: schema.employees, + orders: schema.orders, + products: schema.products, + suppliers: schema.suppliers, + }; + await seed(db, currSchema, { count: 11 }).refine(() => ({ customers: { count: 4, with: { @@ -812,8 +1114,45 @@ test('basic seed test for all postgres data types', async () => { expect(predicate).toBe(true); }); +test('all array data types test', async () => { + await seed(db, { allArrayDataTypes: schema.allArrayDataTypes }, { count: 1000 }); + + const allArrayDataTypes = await db.select().from(schema.allArrayDataTypes); + // every value in each rows does not equal undefined. + const predicate = allArrayDataTypes.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && val.length === 10) + ); + + expect(predicate).toBe(true); +}); + +test('nd arrays', async () => { + await seed(db, { ndArrays: schema.ndArrays }, { count: 1000 }); + + const ndArrays = await db.select().from(schema.ndArrays); + // every value in each rows does not equal undefined. + const predicate0 = ndArrays.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && val.length !== 0) + ); + let predicate1 = true, predicate2 = true, predicate3 = true, predicate4 = true; + + for (const row of ndArrays) { + predicate1 = predicate1 && (row.integer1DArray?.length === 3); + + predicate2 = predicate2 && (row.integer2DArray?.length === 4) && (row.integer2DArray[0]?.length === 3); + + predicate3 = predicate3 && (row.integer3DArray?.length === 5) && (row.integer3DArray[0]?.length === 4) + && (row.integer3DArray[0][0]?.length === 3); + + predicate4 = predicate4 && (row.integer4DArray?.length === 6) && (row.integer4DArray[0]?.length === 5) + && (row.integer4DArray[0][0]?.length === 4) && (row.integer4DArray[0][0][0]?.length === 3); + } + + expect(predicate0 && predicate1 && predicate2 && predicate3 && predicate4).toBe(true); +}); + // All generators test------------------------------- -const count = 10000; +const count = 1000; test('enum generator test', async () => { await seed(db, { enumTable: schema.enumTable }).refine(() => ({ @@ -846,6 +1185,23 @@ test('default generator test', async () => { expect(predicate).toBe(true); }); +test('default array generator test', async () => { + await seed(db, { defaultTable: schema.defaultArrayTable }).refine((funcs) => ({ + defaultTable: { + count, + columns: { + defaultString: funcs.default({ defaultValue: 'default string', arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.defaultArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('valuesFromArray generator test', async () => { await seed(db, { valuesFromArrayTable: schema.valuesFromArrayTable }).refine((funcs) => ({ valuesFromArrayTable: { @@ -958,6 +1314,23 @@ test('valuesFromArray unique generator test', async () => { ).rejects.toThrow('There are no enough values to fill unique column.'); }); +test('valuesFromArray array generator test', async () => { + await seed(db, { valuesFromArrayTable: schema.valuesFromArrayArrayTable }).refine((funcs) => ({ + valuesFromArrayTable: { + count, + columns: { + valuesFromArray: funcs.valuesFromArray({ values: lastNames, arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.valuesFromArrayArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('intPrimaryKey generator test', async () => { await seed(db, { intPrimaryKeyTable: schema.intPrimaryKeyTable }).refine((funcs) => ({ intPrimaryKeyTable: { @@ -1022,6 +1395,23 @@ test('number unique generator test', async () => { ).rejects.toThrow('count exceeds max number of unique integers in given range(min, max), try to make range wider.'); }); +test('number array generator test', async () => { + await seed(db, { numberTable: schema.numberArrayTable }).refine((funcs) => ({ + numberTable: { + count, + columns: { + number: funcs.number({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.numberArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('int generator test', async () => { await seed(db, { intTable: schema.intTable }).refine((funcs) => ({ intTable: { @@ -1067,6 +1457,23 @@ test('int unique generator test', async () => { ).rejects.toThrow('count exceeds max number of unique integers in given range(min, max), try to make range wider.'); }); +test('int array generator test', async () => { + await seed(db, { intTable: schema.intArrayTable }).refine((funcs) => ({ + intTable: { + count, + columns: { + int: funcs.int({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.intArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('boolean generator test', async () => { await seed(db, { booleanTable: schema.booleanTable }).refine((funcs) => ({ booleanTable: { @@ -1084,6 +1491,23 @@ test('boolean generator test', async () => { expect(predicate).toBe(true); }); +test('boolean array generator test', async () => { + await seed(db, { booleanTable: schema.booleanArrayTable }).refine((funcs) => ({ + booleanTable: { + count, + columns: { + boolean: funcs.boolean({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.booleanArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('date generator test', async () => { await seed(db, { dateTable: schema.dateTable }).refine((funcs) => ({ dateTable: { @@ -1101,6 +1525,26 @@ test('date generator test', async () => { expect(predicate).toBe(true); }); +test('date array generator test', async () => { + await seed(db, { dateTable: schema.dateArrayTable }).refine((funcs) => ({ + dateTable: { + count, + columns: { + date: funcs.date({ arraySize: 3 }), + dateString: funcs.date({ arraySize: 4 }), + }, + }, + })); + + const data = await db.select().from(schema.dateArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && [3, 4].includes(val.length)) + ); + expect(predicate).toBe(true); +}); + test('time generator test', async () => { await seed(db, { timeTable: schema.timeTable }).refine((funcs) => ({ timeTable: { @@ -1118,6 +1562,23 @@ test('time generator test', async () => { expect(predicate).toBe(true); }); +test('time array generator test', async () => { + await seed(db, { timeTable: schema.timeArrayTable }).refine((funcs) => ({ + timeTable: { + count, + columns: { + time: funcs.time({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.timeArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('timestamp generator test', async () => { await seed(db, { timestampTable: schema.timestampTable }).refine((funcs) => ({ timestampTable: { @@ -1135,6 +1596,23 @@ test('timestamp generator test', async () => { expect(predicate).toBe(true); }); +test('timestamp array generator test', async () => { + await seed(db, { timestampTable: schema.timestampArrayTable }).refine((funcs) => ({ + timestampTable: { + count, + columns: { + timestamp: funcs.timestamp({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.timestampArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('json generator test', async () => { await seed(db, { jsonTable: schema.jsonTable }).refine((funcs) => ({ jsonTable: { @@ -1152,6 +1630,23 @@ test('json generator test', async () => { expect(predicate).toBe(true); }); +test('json array generator test', async () => { + await seed(db, { jsonTable: schema.jsonArrayTable }).refine((funcs) => ({ + jsonTable: { + count, + columns: { + json: funcs.json({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.jsonArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('interval generator test', async () => { await seed(db, { intervalTable: schema.intervalTable }).refine((funcs) => ({ intervalTable: { @@ -1186,6 +1681,23 @@ test('interval unique generator test', async () => { expect(predicate).toBe(true); }); +test('interval array generator test', async () => { + await seed(db, { intervalTable: schema.intervalArrayTable }).refine((funcs) => ({ + intervalTable: { + count, + columns: { + interval: funcs.interval({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.intervalArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('string generator test', async () => { await seed(db, { stringTable: schema.stringTable }).refine((funcs) => ({ stringTable: { @@ -1219,6 +1731,23 @@ test('string unique generator test', async () => { expect(predicate).toBe(true); }); +test('string array generator test', async () => { + await seed(db, { stringTable: schema.stringArrayTable }).refine((funcs) => ({ + stringTable: { + count, + columns: { + string: funcs.string({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.stringArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('email generator test', async () => { await seed(db, { emailTable: schema.emailTable }).refine((funcs) => ({ emailTable: { @@ -1236,6 +1765,23 @@ test('email generator test', async () => { expect(predicate).toBe(true); }); +test('email array generator test', async () => { + await seed(db, { emailTable: schema.emailArrayTable }).refine((funcs) => ({ + emailTable: { + count, + columns: { + email: funcs.email({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.emailArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('firstName generator test', async () => { await seed(db, { firstNameTable: schema.firstNameTable }).refine((funcs) => ({ firstNameTable: { @@ -1281,6 +1827,23 @@ test('firstName unique generator test', async () => { ).rejects.toThrow('count exceeds max number of unique first names.'); }); +test('firstName array generator test', async () => { + await seed(db, { firstNameTable: schema.firstNameArrayTable }).refine((funcs) => ({ + firstNameTable: { + count, + columns: { + firstName: funcs.firstName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.firstNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('lastName generator test', async () => { await seed(db, { lastNameTable: schema.lastNameTable }).refine((funcs) => ({ lastNameTable: { @@ -1326,6 +1889,23 @@ test('lastName unique generator test', async () => { ).rejects.toThrow('count exceeds max number of unique last names.'); }); +test('lastName array generator test', async () => { + await seed(db, { lastNameTable: schema.lastNameArrayTable }).refine((funcs) => ({ + lastNameTable: { + count, + columns: { + lastName: funcs.lastName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.lastNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('fullName generator test', async () => { await seed(db, { fullNameTable: schema.fullNameTable }).refine((funcs) => ({ fullNameTable: { @@ -1361,6 +1941,23 @@ test('fullName unique generator test', async () => { expect(predicate).toBe(true); }); +test('fullName array generator test', async () => { + await seed(db, { fullNameTable: schema.fullNameArrayTable }).refine((funcs) => ({ + fullNameTable: { + count, + columns: { + fullName: funcs.fullName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.fullNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('country generator test', async () => { await seed(db, { countryTable: schema.countryTable }).refine((funcs) => ({ countryTable: { @@ -1382,7 +1979,7 @@ test('country unique generator test', async () => { // countryUniqueTable----------------------------------------------------------------------------------- await seed(db, { countryUniqueTable: schema.countryUniqueTable }).refine((funcs) => ({ countryUniqueTable: { - count: 160, + count: countries.length, columns: { countryUnique: funcs.country({ isUnique: true }), }, @@ -1397,7 +1994,7 @@ test('country unique generator test', async () => { await expect( seed(db, { countryUniqueTable: schema.countryUniqueTable }).refine((funcs) => ({ countryUniqueTable: { - count: 168, + count: countries.length + 1, columns: { countryUnique: funcs.country({ isUnique: true }), }, @@ -1406,6 +2003,23 @@ test('country unique generator test', async () => { ).rejects.toThrow('count exceeds max number of unique countries.'); }); +test('country array generator test', async () => { + await seed(db, { countryTable: schema.countryArrayTable }).refine((funcs) => ({ + countryTable: { + count, + columns: { + country: funcs.country({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.countryArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('city generator test', async () => { await seed(db, { cityTable: schema.cityTable }).refine((funcs) => ({ cityTable: { @@ -1428,7 +2042,7 @@ test('city unique generator test', async () => { await reset(db, { cityUniqueTable: schema.cityUniqueTable }); await seed(db, { cityUniqueTable: schema.cityUniqueTable }).refine((funcs) => ({ cityUniqueTable: { - count: 38884, + count: cities.length, columns: { cityUnique: funcs.city({ isUnique: true }), }, @@ -1443,7 +2057,7 @@ test('city unique generator test', async () => { await expect( seed(db, { cityUniqueTable: schema.cityUniqueTable }).refine((funcs) => ({ cityUniqueTable: { - count: 42985, + count: cities.length + 1, columns: { cityUnique: funcs.city({ isUnique: true }), }, @@ -1452,6 +2066,23 @@ test('city unique generator test', async () => { ).rejects.toThrow('count exceeds max number of unique cities.'); }); +test('city array generator test', async () => { + await seed(db, { cityTable: schema.cityArrayTable }).refine((funcs) => ({ + cityTable: { + count, + columns: { + city: funcs.city({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.cityArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('streetAddress generator test', async () => { await seed(db, { streetAddressTable: schema.streetAddressTable }).refine((funcs) => ({ streetAddressTable: { @@ -1486,6 +2117,23 @@ test('streetAddress unique generator test', async () => { expect(predicate).toBe(true); }); +test('streetAddress array generator test', async () => { + await seed(db, { streetAddressTable: schema.streetAddressArrayTable }).refine((funcs) => ({ + streetAddressTable: { + count, + columns: { + streetAddress: funcs.streetAddress({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.streetAddressArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('jobTitle generator test', async () => { await seed(db, { jobTitleTable: schema.jobTitleTable }).refine((funcs) => ({ jobTitleTable: { @@ -1503,6 +2151,23 @@ test('jobTitle generator test', async () => { expect(predicate).toBe(true); }); +test('jobTitle array generator test', async () => { + await seed(db, { jobTitleTable: schema.jobTitleArrayTable }).refine((funcs) => ({ + jobTitleTable: { + count, + columns: { + jobTitle: funcs.jobTitle({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.jobTitleArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('postcode generator test', async () => { await seed(db, { postcodeTable: schema.postcodeTable }).refine((funcs) => ({ postcodeTable: { @@ -1537,6 +2202,23 @@ test('postcode unique generator test', async () => { expect(predicate).toBe(true); }); +test('postcode array generator test', async () => { + await seed(db, { postcodeTable: schema.postcodeArrayTable }).refine((funcs) => ({ + postcodeTable: { + count, + columns: { + postcode: funcs.postcode({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.postcodeArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('state generator test', async () => { await seed(db, { stateTable: schema.stateTable }).refine((funcs) => ({ stateTable: { @@ -1554,6 +2236,23 @@ test('state generator test', async () => { expect(predicate).toBe(true); }); +test('state array generator test', async () => { + await seed(db, { stateTable: schema.stateArrayTable }).refine((funcs) => ({ + stateTable: { + count, + columns: { + state: funcs.state({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.stateArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('companyName generator test', async () => { await seed(db, { companyNameTable: schema.companyNameTable }).refine((funcs) => ({ companyNameTable: { @@ -1588,6 +2287,23 @@ test('companyName unique generator test', async () => { expect(predicate).toBe(true); }); +test('companyName array generator test', async () => { + await seed(db, { companyNameTable: schema.companyNameArrayTable }).refine((funcs) => ({ + companyNameTable: { + count, + columns: { + companyName: funcs.companyName({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.companyNameArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('loremIpsum generator test', async () => { await seed(db, { loremIpsumTable: schema.loremIpsumTable }).refine((funcs) => ({ loremIpsumTable: { @@ -1605,6 +2321,23 @@ test('loremIpsum generator test', async () => { expect(predicate).toBe(true); }); +test('loremIpsum array generator test', async () => { + await seed(db, { loremIpsumTable: schema.loremIpsumArrayTable }).refine((funcs) => ({ + loremIpsumTable: { + count, + columns: { + loremIpsum: funcs.loremIpsum({ arraySize: 3 }), + }, + }, + })); + + const data = await db.select().from(schema.loremIpsumArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 3)); + expect(predicate).toBe(true); +}); + test('point generator test', async () => { await seed(db, { pointTable: schema.pointTable }).refine((funcs) => ({ pointTable: { @@ -1645,6 +2378,23 @@ test('point unique generator test', async () => { expect(predicate).toBe(true); }); +test('point array generator test', async () => { + await seed(db, { pointTable: schema.pointArrayTable }).refine((funcs) => ({ + pointTable: { + count, + columns: { + point: funcs.point({ arraySize: 2 }), + }, + }, + })); + + const data = await db.select().from(schema.pointArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 2)); + expect(predicate).toBe(true); +}); + test('line generator test', async () => { await seed(db, { lineTable: schema.lineTable }).refine((funcs) => ({ lineTable: { @@ -1685,6 +2435,23 @@ test('line unique generator test', async () => { expect(predicate).toBe(true); }); +test('line array generator test', async () => { + await seed(db, { lineTable: schema.lineArrayTable }).refine((funcs) => ({ + lineTable: { + count, + columns: { + line: funcs.line({ arraySize: 2 }), + }, + }, + })); + + const data = await db.select().from(schema.lineArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => Object.values(row).every((val) => val !== undefined && val !== null && val.length === 2)); + expect(predicate).toBe(true); +}); + test('phoneNumber generator test', async () => { await seed(db, { phoneNumberTable: schema.phoneNumberTable }).refine((funcs) => ({ phoneNumberTable: { @@ -1707,6 +2474,34 @@ test('phoneNumber generator test', async () => { expect(predicate).toBe(true); }); +test('phoneNumber array generator test', async () => { + await seed(db, { phoneNumberTable: schema.phoneNumberArrayTable }).refine((funcs) => ({ + phoneNumberTable: { + count, + columns: { + phoneNumber: funcs.phoneNumber({ arraySize: 3 }), + phoneNumberPrefixes: funcs.phoneNumber({ + prefixes: ['+380 99', '+380 67', '+1'], + generatedDigitsNumbers: [7, 7, 10], + arraySize: 4, + }), + phoneNumberTemplate: funcs.phoneNumber({ + template: '+380 ## ## ### ##', + arraySize: 5, + }), + }, + }, + })); + + const data = await db.select().from(schema.phoneNumberArrayTable); + // every value in each row does not equal undefined. + const predicate = data.length !== 0 + && data.every((row) => + Object.values(row).every((val) => val !== undefined && val !== null && [3, 4, 5].includes(val.length)) + ); + expect(predicate).toBe(true); +}); + test('weightedRandom generator test', async () => { await seed(db, { weightedRandomTable: schema.weightedRandomTable }).refine((funcs) => ({ weightedRandomTable: { diff --git a/integration-tests/tests/seeder/pgSchema.ts b/integration-tests/tests/seeder/pgSchema.ts index ef5c4943a..ee0fceff5 100644 --- a/integration-tests/tests/seeder/pgSchema.ts +++ b/integration-tests/tests/seeder/pgSchema.ts @@ -23,6 +23,7 @@ import { text, time, timestamp, + uuid, varchar, } from 'drizzle-orm/pg-core'; @@ -161,6 +162,42 @@ export const allDataTypes = schema.table('all_data_types', { line: line('line', { mode: 'abc' }), lineTuple: line('line_tuple', { mode: 'tuple' }), moodEnum: moodEnum('mood_enum'), + uuid: uuid('uuid'), +}); + +export const allArrayDataTypes = schema.table('all_array_data_types', { + integerArray: integer('integer_array').array(), + smallintArray: smallint('smallint_array').array(), + bigintegerArray: bigint('bigint_array', { mode: 'bigint' }).array(), + bigintNumberArray: bigint('bigint_number_array', { mode: 'number' }).array(), + booleanArray: boolean('boolean_array').array(), + textArray: text('text_array').array(), + varcharArray: varchar('varchar_array', { length: 256 }).array(), + charArray: char('char_array', { length: 256 }).array(), + numericArray: numeric('numeric_array').array(), + decimalArray: decimal('decimal_array').array(), + realArray: real('real_array').array(), + doublePrecisionArray: doublePrecision('double_precision_array').array(), + jsonArray: json('json_array').array(), + jsonbArray: jsonb('jsonb_array').array(), + timeArray: time('time_array').array(), + timestampDateArray: timestamp('timestamp_date_array', { mode: 'date' }).array(), + timestampStringArray: timestamp('timestamp_string_array', { mode: 'string' }).array(), + dateStringArray: date('date_string_array', { mode: 'string' }).array(), + dateArray: date('date_array', { mode: 'date' }).array(), + intervalArray: interval('interval_array').array(), + pointArray: point('point_array', { mode: 'xy' }).array(), + pointTupleArray: point('point_tuple_array', { mode: 'tuple' }).array(), + lineArray: line('line_array', { mode: 'abc' }).array(), + lineTupleArray: line('line_tuple_array', { mode: 'tuple' }).array(), + moodEnumArray: moodEnum('mood_enum_array').array(), +}); + +export const ndArrays = schema.table('nd_arrays', { + integer1DArray: integer('integer_1d_array').array(3), + integer2DArray: integer('integer_2d_array').array(3).array(4), + integer3DArray: integer('integer_3d_array').array(3).array(4).array(5), + integer4DArray: integer('integer_4d_array').array(3).array(4).array(5).array(6), }); // All generators tables ------------------------------- @@ -172,6 +209,10 @@ export const defaultTable = schema.table('default_table', { defaultString: text('default_string'), }); +export const defaultArrayTable = schema.table('default_array_table', { + defaultString: text('default_string').array(), +}); + export const valuesFromArrayTable = schema.table('values_from_array_table', { valuesFromArrayNotNull: varchar('values_from_array_not_null', { length: 256 }).notNull(), valuesFromArrayWeightedNotNull: varchar('values_from_array_weighted_not_null', { length: 256 }).notNull(), @@ -184,6 +225,10 @@ export const valuesFromArrayUniqueTable = schema.table('values_from_array_unique valuesFromArrayWeightedNotNull: varchar('values_from_array_weighted_not_null', { length: 256 }).unique().notNull(), }); +export const valuesFromArrayArrayTable = schema.table('values_from_array_array_table', { + valuesFromArray: varchar('values_from_array', { length: 256 }).array(), +}); + export const intPrimaryKeyTable = schema.table('int_primary_key_table', { intPrimaryKey: integer('int_primary_key').unique(), }); @@ -196,6 +241,10 @@ export const numberUniqueTable = schema.table('number_unique_table', { numberUnique: real('number_unique').unique(), }); +export const numberArrayTable = schema.table('number_array_table', { + number: real('number').array(), +}); + export const intTable = schema.table('int_table', { int: integer('int'), }); @@ -204,26 +253,52 @@ export const intUniqueTable = schema.table('int_unique_table', { intUnique: integer('int_unique').unique(), }); +export const intArrayTable = schema.table('int_array_table', { + int: integer('int').array(), +}); + export const booleanTable = schema.table('boolean_table', { boolean: boolean('boolean'), }); +export const booleanArrayTable = schema.table('boolean_array_table', { + boolean: boolean('boolean').array(), +}); + export const dateTable = schema.table('date_table', { date: date('date'), }); +// TODO: add tests for data type with different modes +export const dateArrayTable = schema.table('date_array_table', { + date: date('date', { mode: 'date' }).array(), + dateString: date('date_string', { mode: 'string' }).array(), +}); + export const timeTable = schema.table('time_table', { time: time('time'), }); +export const timeArrayTable = schema.table('time_array_table', { + time: time('time').array(), +}); + export const timestampTable = schema.table('timestamp_table', { timestamp: timestamp('timestamp'), }); +export const timestampArrayTable = schema.table('timestamp_array_table', { + timestamp: timestamp('timestamp').array(), +}); + export const jsonTable = schema.table('json_table', { json: json('json'), }); +export const jsonArrayTable = schema.table('json_array_table', { + json: json('json').array(), +}); + export const intervalTable = schema.table('interval_table', { interval: interval('interval'), }); @@ -232,6 +307,10 @@ export const intervalUniqueTable = schema.table('interval_unique_table', { intervalUnique: interval('interval_unique').unique(), }); +export const intervalArrayTable = schema.table('interval_array_table', { + interval: interval('interval').array(), +}); + export const stringTable = schema.table('string_table', { string: text('string'), }); @@ -240,10 +319,18 @@ export const stringUniqueTable = schema.table('string_unique_table', { stringUnique: varchar('string_unique', { length: 256 }).unique(), }); +export const stringArrayTable = schema.table('string_array_table', { + string: text('string').array(), +}); + export const emailTable = schema.table('email_table', { email: varchar('email', { length: 256 }).unique(), }); +export const emailArrayTable = schema.table('email_array_table', { + email: varchar('email', { length: 256 }).array(), +}); + export const firstNameTable = schema.table('first_name_table', { firstName: varchar('first_name', { length: 256 }), }); @@ -252,6 +339,10 @@ export const firstNameUniqueTable = schema.table('first_name_unique_table', { firstNameUnique: varchar('first_name_unique', { length: 256 }).unique(), }); +export const firstNameArrayTable = schema.table('first_name_array_table', { + firstName: varchar('first_name', { length: 256 }).array(), +}); + export const lastNameTable = schema.table('last_name_table', { lastName: varchar('last_name', { length: 256 }), }); @@ -260,6 +351,10 @@ export const lastNameUniqueTable = schema.table('last_name_unique_table', { lastNameUnique: varchar('last_name_unique', { length: 256 }).unique(), }); +export const lastNameArrayTable = schema.table('last_name_array_table', { + lastName: varchar('last_name', { length: 256 }).array(), +}); + export const fullNameTable = schema.table('full_name__table', { fullName: varchar('full_name_', { length: 256 }), }); @@ -268,6 +363,10 @@ export const fullNameUniqueTable = schema.table('full_name_unique_table', { fullNameUnique: varchar('full_name_unique', { length: 256 }).unique(), }); +export const fullNameArrayTable = schema.table('full_name_array_table', { + fullName: varchar('full_name', { length: 256 }).array(), +}); + export const countryTable = schema.table('country_table', { country: varchar('country', { length: 256 }), }); @@ -276,6 +375,10 @@ export const countryUniqueTable = schema.table('country_unique_table', { countryUnique: varchar('country_unique', { length: 256 }).unique(), }); +export const countryArrayTable = schema.table('country_array_table', { + country: varchar('country', { length: 256 }).array(), +}); + export const cityTable = schema.table('city_table', { city: varchar('city', { length: 256 }), }); @@ -284,6 +387,10 @@ export const cityUniqueTable = schema.table('city_unique_table', { cityUnique: varchar('city_unique', { length: 256 }).unique(), }); +export const cityArrayTable = schema.table('city_array_table', { + city: varchar('city', { length: 256 }).array(), +}); + export const streetAddressTable = schema.table('street_address_table', { streetAddress: varchar('street_address', { length: 256 }), }); @@ -292,10 +399,18 @@ export const streetAddressUniqueTable = schema.table('street_address_unique_tabl streetAddressUnique: varchar('street_address_unique', { length: 256 }).unique(), }); +export const streetAddressArrayTable = schema.table('street_address_array_table', { + streetAddress: varchar('street_address', { length: 256 }).array(), +}); + export const jobTitleTable = schema.table('job_title_table', { jobTitle: text('job_title'), }); +export const jobTitleArrayTable = schema.table('job_title_array_table', { + jobTitle: text('job_title').array(), +}); + export const postcodeTable = schema.table('postcode_table', { postcode: varchar('postcode', { length: 256 }), }); @@ -304,10 +419,18 @@ export const postcodeUniqueTable = schema.table('postcode_unique_table', { postcodeUnique: varchar('postcode_unique', { length: 256 }).unique(), }); +export const postcodeArrayTable = schema.table('postcode_array_table', { + postcode: varchar('postcode', { length: 256 }).array(), +}); + export const stateTable = schema.table('state_table', { state: text('state'), }); +export const stateArrayTable = schema.table('state_array_table', { + state: text('state').array(), +}); + export const companyNameTable = schema.table('company_name_table', { companyName: text('company_name'), }); @@ -316,18 +439,34 @@ export const companyNameUniqueTable = schema.table('company_name_unique_table', companyNameUnique: varchar('company_name_unique', { length: 256 }).unique(), }); +export const companyNameArrayTable = schema.table('company_name_array_table', { + companyName: text('company_name').array(), +}); + export const loremIpsumTable = schema.table('lorem_ipsum_table', { loremIpsum: text('lorem_ipsum'), }); +export const loremIpsumArrayTable = schema.table('lorem_ipsum_array_table', { + loremIpsum: text('lorem_ipsum').array(), +}); + export const pointTable = schema.table('point_table', { point: point('point'), }); +export const pointArrayTable = schema.table('point_array_table', { + point: point('point').array(), +}); + export const lineTable = schema.table('line_table', { line: line('line'), }); +export const lineArrayTable = schema.table('line_array_table', { + line: line('line').array(), +}); + // export const pointUniqueTable = schema.table("point_unique_table", { // pointUnique: point("point_unique").unique(), // }); @@ -342,6 +481,12 @@ export const phoneNumberTable = schema.table('phone_number_table', { phoneNumberPrefixes: varchar('phone_number_prefixes', { length: 256 }).unique(), }); +export const phoneNumberArrayTable = schema.table('phone_number_array_table', { + phoneNumber: varchar('phoneNumber', { length: 256 }).array(), + phoneNumberTemplate: varchar('phone_number_template', { length: 256 }).array(), + phoneNumberPrefixes: varchar('phone_number_prefixes', { length: 256 }).array(), +}); + export const weightedRandomTable = schema.table('weighted_random_table', { weightedRandom: varchar('weighted_random', { length: 256 }), }); diff --git a/integration-tests/tests/singlestore/singlestore-common.ts b/integration-tests/tests/singlestore/singlestore-common.ts index 6335bf9ec..fe7c2afb4 100644 --- a/integration-tests/tests/singlestore/singlestore-common.ts +++ b/integration-tests/tests/singlestore/singlestore-common.ts @@ -35,6 +35,7 @@ import { decimal, except, getTableConfig, + index, int, intersect, json, @@ -2701,6 +2702,37 @@ export function tests(driver?: string) { })()).rejects.toThrowError(); }); + test('define constraints as array', async (ctx) => { + const { db } = ctx.singlestore; + + const table = singlestoreTable('name', { + id: int(), + }, (t) => [ + index('name').on(t.id), + primaryKey({ columns: [t.id], name: 'custom' }), + ]); + + const { indexes, primaryKeys } = getTableConfig(table); + + expect(indexes.length).toBe(1); + expect(primaryKeys.length).toBe(1); + }); + + test('define constraints as array inside third param', async (ctx) => { + const { db } = ctx.singlestore; + + const table = singlestoreTable('name', { + id: int(), + }, (t) => [ + [index('name').on(t.id), primaryKey({ columns: [t.id], name: 'custom' })], + ]); + + const { indexes, primaryKeys } = getTableConfig(table); + + expect(indexes.length).toBe(1); + expect(primaryKeys.length).toBe(1); + }); + test.skip('set operations (mixed) from query builder', async (ctx) => { const { db } = ctx.singlestore; diff --git a/integration-tests/tests/sqlite/sqlite-common.ts b/integration-tests/tests/sqlite/sqlite-common.ts index 1c62181dd..c6d67cee3 100644 --- a/integration-tests/tests/sqlite/sqlite-common.ts +++ b/integration-tests/tests/sqlite/sqlite-common.ts @@ -29,6 +29,7 @@ import { foreignKey, getTableConfig, getViewConfig, + index, int, integer, intersect, @@ -2592,6 +2593,34 @@ export function tests() { }).rejects.toThrowError(); }); + test('define constraints as array', async (_ctx) => { + const table = sqliteTable('name', { + id: int(), + }, (t) => [ + index('name').on(t.id), + primaryKey({ columns: [t.id], name: 'custom' }), + ]); + + const { indexes, primaryKeys } = getTableConfig(table); + + expect(indexes.length).toBe(1); + expect(primaryKeys.length).toBe(1); + }); + + test('define constraints as array inside third param', async (_ctx) => { + const table = sqliteTable('name', { + id: int(), + }, (t) => [ + index('name').on(t.id), + primaryKey({ columns: [t.id], name: 'custom' }), + ]); + + const { indexes, primaryKeys } = getTableConfig(table); + + expect(indexes.length).toBe(1); + expect(primaryKeys.length).toBe(1); + }); + test('aggregate function: count', async (ctx) => { const { db } = ctx.sqlite; const table = aggregateTable;