From 32b71c080ca9b52b026ba5f85a48806a59f196a1 Mon Sep 17 00:00:00 2001 From: OleksiiKH0240 <homenko0240@gmail.com> Date: Mon, 27 Jan 2025 16:56:45 +0200 Subject: [PATCH 01/11] bug fix --- drizzle-seed/src/index.ts | 286 ++++++++++++++++++++++---------------- 1 file changed, 167 insertions(+), 119 deletions(-) diff --git a/drizzle-seed/src/index.ts b/drizzle-seed/src/index.ts index cc416c84d..eb44e47f1 100644 --- a/drizzle-seed/src/index.ts +++ b/drizzle-seed/src/index.ts @@ -615,47 +615,63 @@ const getPostgresInfo = ( const schemaConfig = extractTablesRelationalConfig(schema, createTableRelationsHelpers); const relations: RelationWithReferences[] = []; for (const table of Object.values(schemaConfig.tables)) { - if (table.relations !== undefined) { - for (const drizzleRel of Object.values(table.relations)) { - if (is(drizzleRel, One)) { - const tableConfig = getPgTableConfig(drizzleRel.sourceTable as PgTable); - const tableDbSchema = tableConfig.schema ?? 'public'; - const tableDbName = tableConfig.name; - const tableTsName = schemaConfig.tableNamesMap[`${tableDbSchema}.${tableDbName}`] ?? tableDbName; - - const dbToTsColumnNamesMap = getDbToTsColumnNamesMap(drizzleRel.sourceTable); - const columns = drizzleRel.config?.fields.map((field) => dbToTsColumnNamesMap[field.name] as string) - ?? []; - - const refTableConfig = getPgTableConfig(drizzleRel.referencedTable as PgTable); - const refTableDbSchema = refTableConfig.schema ?? 'public'; - const refTableDbName = refTableConfig.name; - const refTableTsName = schemaConfig.tableNamesMap[`${refTableDbSchema}.${refTableDbName}`] - ?? refTableDbName; - - const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap(drizzleRel.referencedTable); - const refColumns = drizzleRel.config?.references.map((ref) => - dbToTsColumnNamesMapForRefTable[ref.name] as string - ) - ?? []; - - if (tableRelations[refTableTsName] === undefined) { - tableRelations[refTableTsName] = []; - } - - const relation: RelationWithReferences = { - table: tableTsName, - columns, - refTable: refTableTsName, - refColumns, - refTableRels: tableRelations[refTableTsName], - type: 'one', - }; - - relations.push(relation); - tableRelations[tableTsName]!.push(relation); - } + if (table.relations === undefined) continue; + + for (const drizzleRel of Object.values(table.relations)) { + if (!is(drizzleRel, One)) continue; + + const tableConfig = getPgTableConfig(drizzleRel.sourceTable as PgTable); + const tableDbSchema = tableConfig.schema ?? 'public'; + const tableDbName = tableConfig.name; + const tableTsName = schemaConfig.tableNamesMap[`${tableDbSchema}.${tableDbName}`] ?? tableDbName; + + const dbToTsColumnNamesMap = getDbToTsColumnNamesMap(drizzleRel.sourceTable); + const columns = drizzleRel.config?.fields.map((field) => dbToTsColumnNamesMap[field.name] as string) + ?? []; + + const refTableConfig = getPgTableConfig(drizzleRel.referencedTable as PgTable); + const refTableDbSchema = refTableConfig.schema ?? 'public'; + const refTableDbName = refTableConfig.name; + const refTableTsName = schemaConfig.tableNamesMap[`${refTableDbSchema}.${refTableDbName}`] + ?? refTableDbName; + + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap(drizzleRel.referencedTable); + const refColumns = drizzleRel.config?.references.map((ref) => + dbToTsColumnNamesMapForRefTable[ref.name] as string + ) + ?? []; + + if (tableRelations[refTableTsName] === undefined) { + tableRelations[refTableTsName] = []; } + + const relation: RelationWithReferences = { + table: tableTsName, + columns, + refTable: refTableTsName, + refColumns, + refTableRels: tableRelations[refTableTsName], + type: 'one', + }; + + // do not add duplicate relation + if ( + tableRelations[tableTsName]?.some((rel) => + rel.table === relation.table + && rel.refTable === relation.refTable + ) === true + ) { + console.warn( + `You are providing a one-to-many relation from the '${relation.table}' table to the '${relation.refTable}' table,\n` + + `while your '${relation.table}' table object already has foreign key constraint in the schema(from the '${relation.table}' table to the '${relation.refTable}' table).\n` + + `You can specify either the foreign key constraint or the relation, but not both.\n` + + `By default, one-to-many relation will be ignored to respect foreign key constraint.\n`, + ); + continue; + } + + relations.push(relation); + tableRelations[tableTsName]!.push(relation); } } return relations; @@ -993,47 +1009,63 @@ const getMySqlInfo = ( const schemaConfig = extractTablesRelationalConfig(schema, createTableRelationsHelpers); const relations: RelationWithReferences[] = []; for (const table of Object.values(schemaConfig.tables)) { - if (table.relations !== undefined) { - for (const drizzleRel of Object.values(table.relations)) { - if (is(drizzleRel, One)) { - const tableConfig = getMysqlTableConfig(drizzleRel.sourceTable as MySqlTable); - const tableDbSchema = tableConfig.schema ?? 'public'; - const tableDbName = tableConfig.name; - const tableTsName = schemaConfig.tableNamesMap[`${tableDbSchema}.${tableDbName}`] ?? tableDbName; - - const dbToTsColumnNamesMap = getDbToTsColumnNamesMap(drizzleRel.sourceTable as MySqlTable); - const columns = drizzleRel.config?.fields.map((field) => dbToTsColumnNamesMap[field.name] as string) - ?? []; - - const refTableConfig = getMysqlTableConfig(drizzleRel.referencedTable as MySqlTable); - const refTableDbSchema = refTableConfig.schema ?? 'public'; - const refTableDbName = refTableConfig.name; - const refTableTsName = schemaConfig.tableNamesMap[`${refTableDbSchema}.${refTableDbName}`] - ?? refTableDbName; - - const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap(drizzleRel.referencedTable as MySqlTable); - const refColumns = drizzleRel.config?.references.map((ref) => - dbToTsColumnNamesMapForRefTable[ref.name] as string - ) - ?? []; - - if (tableRelations[refTableTsName] === undefined) { - tableRelations[refTableTsName] = []; - } - - const relation: RelationWithReferences = { - table: tableTsName, - columns, - refTable: refTableTsName, - refColumns, - refTableRels: tableRelations[refTableTsName], - type: 'one', - }; - - relations.push(relation); - tableRelations[tableTsName]!.push(relation); - } + if (table.relations === undefined) continue; + + for (const drizzleRel of Object.values(table.relations)) { + if (!is(drizzleRel, One)) continue; + + const tableConfig = getMysqlTableConfig(drizzleRel.sourceTable as MySqlTable); + const tableDbSchema = tableConfig.schema ?? 'public'; + const tableDbName = tableConfig.name; + const tableTsName = schemaConfig.tableNamesMap[`${tableDbSchema}.${tableDbName}`] ?? tableDbName; + + const dbToTsColumnNamesMap = getDbToTsColumnNamesMap(drizzleRel.sourceTable as MySqlTable); + const columns = drizzleRel.config?.fields.map((field) => dbToTsColumnNamesMap[field.name] as string) + ?? []; + + const refTableConfig = getMysqlTableConfig(drizzleRel.referencedTable as MySqlTable); + const refTableDbSchema = refTableConfig.schema ?? 'public'; + const refTableDbName = refTableConfig.name; + const refTableTsName = schemaConfig.tableNamesMap[`${refTableDbSchema}.${refTableDbName}`] + ?? refTableDbName; + + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap(drizzleRel.referencedTable as MySqlTable); + const refColumns = drizzleRel.config?.references.map((ref) => + dbToTsColumnNamesMapForRefTable[ref.name] as string + ) + ?? []; + + if (tableRelations[refTableTsName] === undefined) { + tableRelations[refTableTsName] = []; } + + const relation: RelationWithReferences = { + table: tableTsName, + columns, + refTable: refTableTsName, + refColumns, + refTableRels: tableRelations[refTableTsName], + type: 'one', + }; + + // do not add duplicate relation + if ( + tableRelations[tableTsName]?.some((rel) => + rel.table === relation.table + && rel.refTable === relation.refTable + ) === true + ) { + console.warn( + `You are providing a one-to-many relation from the '${relation.table}' table to the '${relation.refTable}' table,\n` + + `while your '${relation.table}' table object already has foreign key constraint in the schema(from the '${relation.table}' table to the '${relation.refTable}' table).\n` + + `You can specify either the foreign key constraint or the relation, but not both.\n` + + `By default, one-to-many relation will be ignored to respect foreign key constraint.\n`, + ); + continue; + } + + relations.push(relation); + tableRelations[tableTsName]!.push(relation); } } return relations; @@ -1299,46 +1331,62 @@ const getSqliteInfo = ( const schemaConfig = extractTablesRelationalConfig(schema, createTableRelationsHelpers); const relations: RelationWithReferences[] = []; for (const table of Object.values(schemaConfig.tables)) { - if (table.relations !== undefined) { - for (const drizzleRel of Object.values(table.relations)) { - if (is(drizzleRel, One)) { - const tableConfig = getSqliteTableConfig(drizzleRel.sourceTable as SQLiteTable); - const tableDbName = tableConfig.name; - // TODO: tableNamesMap: have {public.customer: 'customer'} structure in sqlite - const tableTsName = schemaConfig.tableNamesMap[`public.${tableDbName}`] ?? tableDbName; - - const dbToTsColumnNamesMap = getDbToTsColumnNamesMap(drizzleRel.sourceTable as SQLiteTable); - const columns = drizzleRel.config?.fields.map((field) => dbToTsColumnNamesMap[field.name] as string) - ?? []; - - const refTableConfig = getSqliteTableConfig(drizzleRel.referencedTable as SQLiteTable); - const refTableDbName = refTableConfig.name; - const refTableTsName = schemaConfig.tableNamesMap[`public.${refTableDbName}`] - ?? refTableDbName; - - const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap(drizzleRel.referencedTable as SQLiteTable); - const refColumns = drizzleRel.config?.references.map((ref) => - dbToTsColumnNamesMapForRefTable[ref.name] as string - ) - ?? []; - - if (tableRelations[refTableTsName] === undefined) { - tableRelations[refTableTsName] = []; - } - - const relation: RelationWithReferences = { - table: tableTsName, - columns, - refTable: refTableTsName, - refColumns, - refTableRels: tableRelations[refTableTsName], - type: 'one', - }; - - relations.push(relation); - tableRelations[tableTsName]!.push(relation); - } + if (table.relations === undefined) continue; + + for (const drizzleRel of Object.values(table.relations)) { + if (!is(drizzleRel, One)) continue; + + const tableConfig = getSqliteTableConfig(drizzleRel.sourceTable as SQLiteTable); + const tableDbName = tableConfig.name; + // TODO: tableNamesMap: have {public.customer: 'customer'} structure in sqlite + const tableTsName = schemaConfig.tableNamesMap[`public.${tableDbName}`] ?? tableDbName; + + const dbToTsColumnNamesMap = getDbToTsColumnNamesMap(drizzleRel.sourceTable as SQLiteTable); + const columns = drizzleRel.config?.fields.map((field) => dbToTsColumnNamesMap[field.name] as string) + ?? []; + + const refTableConfig = getSqliteTableConfig(drizzleRel.referencedTable as SQLiteTable); + const refTableDbName = refTableConfig.name; + const refTableTsName = schemaConfig.tableNamesMap[`public.${refTableDbName}`] + ?? refTableDbName; + + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap(drizzleRel.referencedTable as SQLiteTable); + const refColumns = drizzleRel.config?.references.map((ref) => + dbToTsColumnNamesMapForRefTable[ref.name] as string + ) + ?? []; + + if (tableRelations[refTableTsName] === undefined) { + tableRelations[refTableTsName] = []; } + + const relation: RelationWithReferences = { + table: tableTsName, + columns, + refTable: refTableTsName, + refColumns, + refTableRels: tableRelations[refTableTsName], + type: 'one', + }; + + // do not add duplicate relation + if ( + tableRelations[tableTsName]?.some((rel) => + rel.table === relation.table + && rel.refTable === relation.refTable + ) === true + ) { + console.warn( + `You are providing a one-to-many relation from the '${relation.table}' table to the '${relation.refTable}' table,\n` + + `while your '${relation.table}' table object already has foreign key constraint in the schema(from the '${relation.table}' table to the '${relation.refTable}' table).\n` + + `You can specify either the foreign key constraint or the relation, but not both.\n` + + `By default, one-to-many relation will be ignored to respect foreign key constraint.\n`, + ); + continue; + } + + relations.push(relation); + tableRelations[tableTsName]!.push(relation); } } return relations; From e5713d6b7d6a26da08d93a13493f823b92ce6545 Mon Sep 17 00:00:00 2001 From: OleksiiKH0240 <homenko0240@gmail.com> Date: Mon, 27 Jan 2025 19:32:25 +0200 Subject: [PATCH 02/11] fixes --- drizzle-seed/src/index.ts | 27 ++++---- .../mysql/allDataTypesTest/drizzle.config.ts | 7 -- drizzle-seed/tests/mysql/drizzle.config.ts | 7 -- .../mysql/generatorsTest/drizzle.config.ts | 7 -- drizzle-seed/tests/mysql/mysql.test.ts | 64 ++++++++++++++++++- drizzle-seed/tests/mysql/mysqlSchema.ts | 19 ++++++ .../tests/pg/allDataTypesTest/pgSchema.ts | 46 ++++++------- drizzle-seed/tests/pg/pg.test.ts | 54 ++++++++++++++-- drizzle-seed/tests/pg/pgSchema.ts | 16 ++++- drizzle-seed/tests/sqlite/sqlite.test.ts | 52 ++++++++++++++- drizzle-seed/tests/sqlite/sqliteSchema.ts | 25 ++++++++ 11 files changed, 251 insertions(+), 73 deletions(-) delete mode 100644 drizzle-seed/tests/mysql/allDataTypesTest/drizzle.config.ts delete mode 100644 drizzle-seed/tests/mysql/drizzle.config.ts delete mode 100644 drizzle-seed/tests/mysql/generatorsTest/drizzle.config.ts diff --git a/drizzle-seed/src/index.ts b/drizzle-seed/src/index.ts index eb44e47f1..a56134ac3 100644 --- a/drizzle-seed/src/index.ts +++ b/drizzle-seed/src/index.ts @@ -659,13 +659,12 @@ const getPostgresInfo = ( tableRelations[tableTsName]?.some((rel) => rel.table === relation.table && rel.refTable === relation.refTable - ) === true + ) ) { console.warn( - `You are providing a one-to-many relation from the '${relation.table}' table to the '${relation.refTable}' table,\n` - + `while your '${relation.table}' table object already has foreign key constraint in the schema(from the '${relation.table}' table to the '${relation.refTable}' table).\n` - + `You can specify either the foreign key constraint or the relation, but not both.\n` - + `By default, one-to-many relation will be ignored to respect foreign key constraint.\n`, + `You are providing a one-to-many relation between the '${relation.refTable}' and '${relation.table}' tables,\n` + + `while the '${relation.table}' table object already has foreign key constraint in the schema referencing '${relation.refTable}' table.\n` + + `In this case, the foreign key constraint will be used.\n`, ); continue; } @@ -1053,13 +1052,12 @@ const getMySqlInfo = ( tableRelations[tableTsName]?.some((rel) => rel.table === relation.table && rel.refTable === relation.refTable - ) === true + ) ) { console.warn( - `You are providing a one-to-many relation from the '${relation.table}' table to the '${relation.refTable}' table,\n` - + `while your '${relation.table}' table object already has foreign key constraint in the schema(from the '${relation.table}' table to the '${relation.refTable}' table).\n` - + `You can specify either the foreign key constraint or the relation, but not both.\n` - + `By default, one-to-many relation will be ignored to respect foreign key constraint.\n`, + `You are providing a one-to-many relation between the '${relation.refTable}' and '${relation.table}' tables,\n` + + `while the '${relation.table}' table object already has foreign key constraint in the schema referencing '${relation.refTable}' table.\n` + + `In this case, the foreign key constraint will be used.\n`, ); continue; } @@ -1374,13 +1372,12 @@ const getSqliteInfo = ( tableRelations[tableTsName]?.some((rel) => rel.table === relation.table && rel.refTable === relation.refTable - ) === true + ) ) { console.warn( - `You are providing a one-to-many relation from the '${relation.table}' table to the '${relation.refTable}' table,\n` - + `while your '${relation.table}' table object already has foreign key constraint in the schema(from the '${relation.table}' table to the '${relation.refTable}' table).\n` - + `You can specify either the foreign key constraint or the relation, but not both.\n` - + `By default, one-to-many relation will be ignored to respect foreign key constraint.\n`, + `You are providing a one-to-many relation between the '${relation.refTable}' and '${relation.table}' tables,\n` + + `while the '${relation.table}' table object already has foreign key constraint in the schema referencing '${relation.refTable}' table.\n` + + `In this case, the foreign key constraint will be used.\n`, ); continue; } diff --git a/drizzle-seed/tests/mysql/allDataTypesTest/drizzle.config.ts b/drizzle-seed/tests/mysql/allDataTypesTest/drizzle.config.ts deleted file mode 100644 index 78ff7a54b..000000000 --- a/drizzle-seed/tests/mysql/allDataTypesTest/drizzle.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'drizzle-kit'; - -export default defineConfig({ - schema: './src/tests/mysql/allDataTypesTest/mysqlSchema.ts', - out: './src/tests/mysql/allDataTypesTest/mysqlMigrations', - dialect: 'mysql', -}); diff --git a/drizzle-seed/tests/mysql/drizzle.config.ts b/drizzle-seed/tests/mysql/drizzle.config.ts deleted file mode 100644 index 9a84354e3..000000000 --- a/drizzle-seed/tests/mysql/drizzle.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'drizzle-kit'; - -export default defineConfig({ - schema: './src/tests/mysql/mysqlSchema.ts', - out: './src/tests/mysql/mysqlMigrations', - dialect: 'mysql', -}); diff --git a/drizzle-seed/tests/mysql/generatorsTest/drizzle.config.ts b/drizzle-seed/tests/mysql/generatorsTest/drizzle.config.ts deleted file mode 100644 index 621d8acc1..000000000 --- a/drizzle-seed/tests/mysql/generatorsTest/drizzle.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'drizzle-kit'; - -export default defineConfig({ - schema: './src/tests/mysql/generatorsTest/mysqlSchema.ts', - out: './src/tests/mysql/generatorsTest/mysqlMigrations', - dialect: 'mysql', -}); diff --git a/drizzle-seed/tests/mysql/mysql.test.ts b/drizzle-seed/tests/mysql/mysql.test.ts index 7d6bfd48e..4d25171ea 100644 --- a/drizzle-seed/tests/mysql/mysql.test.ts +++ b/drizzle-seed/tests/mysql/mysql.test.ts @@ -1,12 +1,12 @@ import Docker from 'dockerode'; -import { sql } from 'drizzle-orm'; +import { relations, 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 { afterAll, afterEach, beforeAll, expect, test, vi } from 'vitest'; import { reset, seed } from '../../src/index.ts'; import * as schema from './mysqlSchema.ts'; @@ -180,6 +180,29 @@ beforeAll(async () => { `, ); + await db.execute( + sql` + CREATE TABLE \`users\` ( + \`id\` int, + \`name\` text, + \`invitedBy\` int, + CONSTRAINT \`users_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE \`posts\` ( + \`id\` int, + \`name\` text, + \`content\` text, + \`userId\` int, + CONSTRAINT \`posts_id\` PRIMARY KEY(\`id\`) + ); + `, + ); + await db.execute( sql` ALTER TABLE \`order_detail\` ADD CONSTRAINT \`order_detail_order_id_order_id_fk\` FOREIGN KEY (\`order_id\`) REFERENCES \`order\`(\`id\`) ON DELETE cascade ON UPDATE no action; @@ -215,6 +238,18 @@ beforeAll(async () => { ALTER TABLE \`product\` ADD CONSTRAINT \`product_supplier_id_supplier_id_fk\` FOREIGN KEY (\`supplier_id\`) REFERENCES \`supplier\`(\`id\`) ON DELETE cascade ON UPDATE no action; `, ); + + await db.execute( + sql` + ALTER TABLE \`users\` ADD CONSTRAINT \`users_invitedBy_users_id_fk\` FOREIGN KEY (\`invitedBy\`) REFERENCES \`users\`(\`id\`) ON DELETE cascade ON UPDATE no action; + `, + ); + + await db.execute( + sql` + ALTER TABLE \`posts\` ADD CONSTRAINT \`posts_userId_users_id_fk\` FOREIGN KEY (\`userId\`) REFERENCES \`users\`(\`id\`) ON DELETE cascade ON UPDATE no action; + `, + ); }); afterAll(async () => { @@ -379,3 +414,28 @@ test("sequential using of 'with'", async () => { expect(products.length).toBe(11); expect(suppliers.length).toBe(11); }); + +test('overlapping a foreign key constraint with a one-to-many relation', async () => { + const postsRelation = relations(schema.posts, ({ one }) => ({ + user: one(schema.users, { fields: [schema.posts.userId], references: [schema.users.id] }), + })); + + const consoleMock = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + await reset(db, { users: schema.users, posts: schema.posts, postsRelation }); + await seed(db, { users: schema.users, posts: schema.posts, postsRelation }); + // expecting to get a warning + expect(consoleMock).toBeCalled(); + expect(consoleMock).toBeCalledWith(expect.stringMatching(/^You are providing a one-to-many relation.+/)); + + const users = await db.select().from(schema.users); + const posts = await db.select().from(schema.posts); + + expect(users.length).toBe(10); + let predicate = users.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(posts.length).toBe(10); + predicate = posts.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/mysql/mysqlSchema.ts b/drizzle-seed/tests/mysql/mysqlSchema.ts index 624d45d3e..6e1bd5a75 100644 --- a/drizzle-seed/tests/mysql/mysqlSchema.ts +++ b/drizzle-seed/tests/mysql/mysqlSchema.ts @@ -100,3 +100,22 @@ export const details = mysqlTable('order_detail', { .notNull() .references(() => products.id, { onDelete: 'cascade' }), }); + +export const users = mysqlTable( + 'users', + { + id: int().primaryKey(), + name: text(), + invitedBy: int().references((): AnyMySqlColumn => users.id), + }, +); + +export const posts = mysqlTable( + 'posts', + { + id: int().primaryKey(), + name: text(), + content: text(), + userId: int().references(() => users.id), + }, +); diff --git a/drizzle-seed/tests/pg/allDataTypesTest/pgSchema.ts b/drizzle-seed/tests/pg/allDataTypesTest/pgSchema.ts index d4f45de22..16a55baf4 100644 --- a/drizzle-seed/tests/pg/allDataTypesTest/pgSchema.ts +++ b/drizzle-seed/tests/pg/allDataTypesTest/pgSchema.ts @@ -64,31 +64,31 @@ export const allDataTypes = schema.table('all_data_types', { }); 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(), + 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(), + 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', { diff --git a/drizzle-seed/tests/pg/pg.test.ts b/drizzle-seed/tests/pg/pg.test.ts index 90d6b4fc2..08fa5133d 100644 --- a/drizzle-seed/tests/pg/pg.test.ts +++ b/drizzle-seed/tests/pg/pg.test.ts @@ -1,8 +1,8 @@ import { PGlite } from '@electric-sql/pglite'; -import { sql } from 'drizzle-orm'; +import { relations, 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 { afterAll, afterEach, beforeAll, expect, test, vi } from 'vitest'; import { reset, seed } from '../../src/index.ts'; import * as schema from './pgSchema.ts'; @@ -193,14 +193,29 @@ beforeAll(async () => { await db.execute( sql` - create table "seeder_lib_pg"."user" + create table "seeder_lib_pg"."users" ( id serial primary key, name text, "invitedBy" integer - constraint "user_invitedBy_user_id_fk" - references "seeder_lib_pg"."user" + constraint "users_invitedBy_user_id_fk" + references "seeder_lib_pg"."users" + ); + `, + ); + + await db.execute( + sql` + create table "seeder_lib_pg"."posts" + ( + id serial + primary key, + name text, + content text, + "userId" integer + constraint "users_userId_user_id_fk" + references "seeder_lib_pg"."users" ); `, ); @@ -385,11 +400,36 @@ test('seeding with identity columns', async () => { }); test('seeding with self relation', async () => { - await seed(db, { user: schema.user }); + await seed(db, { users: schema.users }); - const result = await db.select().from(schema.user); + const result = await db.select().from(schema.users); expect(result.length).toBe(10); const predicate = result.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); expect(predicate).toBe(true); }); + +test('overlapping a foreign key constraint with a one-to-many relation', async () => { + const postsRelation = relations(schema.posts, ({ one }) => ({ + user: one(schema.users, { fields: [schema.posts.userId], references: [schema.users.id] }), + })); + + const consoleMock = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + await reset(db, { users: schema.users, posts: schema.posts, postsRelation }); + await seed(db, { users: schema.users, posts: schema.posts, postsRelation }); + // expecting to get a warning + expect(consoleMock).toBeCalled(); + expect(consoleMock).toBeCalledWith(expect.stringMatching(/^You are providing a one-to-many relation.+/)); + + const users = await db.select().from(schema.users); + const posts = await db.select().from(schema.posts); + + expect(users.length).toBe(10); + let predicate = users.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(posts.length).toBe(10); + predicate = posts.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/pg/pgSchema.ts b/drizzle-seed/tests/pg/pgSchema.ts index 1a9af755e..05608ab14 100644 --- a/drizzle-seed/tests/pg/pgSchema.ts +++ b/drizzle-seed/tests/pg/pgSchema.ts @@ -109,11 +109,21 @@ export const identityColumnsTable = schema.table('identity_columns_table', { name: text(), }); -export const user = schema.table( - 'user', +export const users = schema.table( + 'users', { id: serial().primaryKey(), name: text(), - invitedBy: integer().references((): AnyPgColumn => user.id), + invitedBy: integer().references((): AnyPgColumn => users.id), + }, +); + +export const posts = schema.table( + 'posts', + { + id: serial().primaryKey(), + name: text(), + content: text(), + userId: integer().references(() => users.id), }, ); diff --git a/drizzle-seed/tests/sqlite/sqlite.test.ts b/drizzle-seed/tests/sqlite/sqlite.test.ts index 550648d49..bf89284d5 100644 --- a/drizzle-seed/tests/sqlite/sqlite.test.ts +++ b/drizzle-seed/tests/sqlite/sqlite.test.ts @@ -1,8 +1,8 @@ import BetterSqlite3 from 'better-sqlite3'; -import { sql } from 'drizzle-orm'; +import { relations, 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 { afterAll, afterEach, beforeAll, expect, test, vi } from 'vitest'; import { reset, seed } from '../../src/index.ts'; import * as schema from './sqliteSchema.ts'; @@ -122,6 +122,29 @@ beforeAll(async () => { \`postal_code\` text NOT NULL, \`country\` text NOT NULL, \`phone\` text NOT NULL +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`users\` ( + \`id\` integer PRIMARY KEY, + \`name\` text, + \`invitedBy\` integer, + FOREIGN KEY (\`invitedBy\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE cascade +); + `), + ); + + db.run( + sql.raw(` + CREATE TABLE \`posts\` ( + \`id\` integer PRIMARY KEY, + \`name\` text, + \`content\` text, + \`userId\` integer, + FOREIGN KEY (\`userId\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE cascade ); `), ); @@ -288,3 +311,28 @@ test("sequential using of 'with'", async () => { expect(products.length).toBe(11); expect(suppliers.length).toBe(11); }); + +test('overlapping a foreign key constraint with a one-to-many relation', async () => { + const postsRelation = relations(schema.posts, ({ one }) => ({ + user: one(schema.users, { fields: [schema.posts.userId], references: [schema.users.id] }), + })); + + const consoleMock = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + await reset(db, { users: schema.users, posts: schema.posts, postsRelation }); + await seed(db, { users: schema.users, posts: schema.posts, postsRelation }); + // expecting to get a warning + expect(consoleMock).toBeCalled(); + expect(consoleMock).toBeCalledWith(expect.stringMatching(/^You are providing a one-to-many relation.+/)); + + const users = await db.select().from(schema.users); + const posts = await db.select().from(schema.posts); + + expect(users.length).toBe(10); + let predicate = users.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); + + expect(posts.length).toBe(10); + predicate = posts.every((row) => Object.values(row).every((val) => val !== undefined && val !== null)); + expect(predicate).toBe(true); +}); diff --git a/drizzle-seed/tests/sqlite/sqliteSchema.ts b/drizzle-seed/tests/sqlite/sqliteSchema.ts index fa00dd365..fe508321b 100644 --- a/drizzle-seed/tests/sqlite/sqliteSchema.ts +++ b/drizzle-seed/tests/sqlite/sqliteSchema.ts @@ -105,3 +105,28 @@ export const details = sqliteTable('order_detail', { .notNull() .references(() => products.id, { onDelete: 'cascade' }), }); + +export const users = sqliteTable( + 'users', + { + id: integer().primaryKey(), + name: text(), + invitedBy: integer(), + }, + (table) => ({ + reportsToFk: foreignKey(() => ({ + columns: [table.invitedBy], + foreignColumns: [table.id], + })), + }), +); + +export const posts = sqliteTable( + 'posts', + { + id: integer().primaryKey(), + name: text(), + content: text(), + userId: integer().references(() => users.id), + }, +); From c5f4421edd497e25e453a68342f50699caf28cd9 Mon Sep 17 00:00:00 2001 From: Karibash <y.karibayashi@gmail.com> Date: Tue, 28 Jan 2025 10:08:47 +0900 Subject: [PATCH 03/11] bugfix: Fix bug that generates incorrect syntax when introspect in mysql --- drizzle-kit/src/introspect-mysql.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drizzle-kit/src/introspect-mysql.ts b/drizzle-kit/src/introspect-mysql.ts index 72d94a6ac..d493ca068 100644 --- a/drizzle-kit/src/introspect-mysql.ts +++ b/drizzle-kit/src/introspect-mysql.ts @@ -923,7 +923,7 @@ const createTableIndexes = ( idxKey = casing(idxKey); - statement += `\t\t${idxKey}: `; + statement += `\n\t`; statement += it.isUnique ? 'uniqueIndex(' : 'index('; statement += `"${it.name}")`; statement += `.on(${ From 43cdb1ed3f8fa834658f95a9b8fd021aedb63f4d Mon Sep 17 00:00:00 2001 From: Karibash <y.karibayashi@gmail.com> Date: Tue, 28 Jan 2025 12:05:41 +0900 Subject: [PATCH 04/11] bugfix: Fix a bug that caused incorrect syntax output when introspect in unsigned columns --- drizzle-kit/src/introspect-mysql.ts | 16 ++++------------ drizzle-kit/src/introspect-singlestore.ts | 17 ++++------------- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/drizzle-kit/src/introspect-mysql.ts b/drizzle-kit/src/introspect-mysql.ts index 72d94a6ac..e46f9f412 100644 --- a/drizzle-kit/src/introspect-mysql.ts +++ b/drizzle-kit/src/introspect-mysql.ts @@ -405,9 +405,7 @@ const column = ( if (lowered.startsWith('int')) { const isUnsigned = lowered.startsWith('int unsigned'); const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); - let out = `${casing(name)}: int(${columnName}${ - isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' - })`; + let out = `${casing(name)}: int(${columnName}${isUnsigned ? '{ unsigned: true }' : ''})`; out += autoincrement ? `.autoincrement()` : ''; out += typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -419,9 +417,7 @@ const column = ( const isUnsigned = lowered.startsWith('tinyint unsigned'); const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); // let out = `${name.camelCase()}: tinyint("${name}")`; - let out: string = `${casing(name)}: tinyint(${columnName}${ - isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' - })`; + let out: string = `${casing(name)}: tinyint(${columnName}${isUnsigned ? '{ unsigned: true }' : ''})`; out += autoincrement ? `.autoincrement()` : ''; out += typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -432,9 +428,7 @@ const column = ( if (lowered.startsWith('smallint')) { const isUnsigned = lowered.startsWith('smallint unsigned'); const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); - let out = `${casing(name)}: smallint(${columnName}${ - isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' - })`; + let out = `${casing(name)}: smallint(${columnName}${isUnsigned ? '{ unsigned: true }' : ''})`; out += autoincrement ? `.autoincrement()` : ''; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -445,9 +439,7 @@ const column = ( if (lowered.startsWith('mediumint')) { const isUnsigned = lowered.startsWith('mediumint unsigned'); const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); - let out = `${casing(name)}: mediumint(${columnName}${ - isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' - })`; + let out = `${casing(name)}: mediumint(${columnName}${isUnsigned ? '{ unsigned: true }' : ''})`; out += autoincrement ? `.autoincrement()` : ''; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` diff --git a/drizzle-kit/src/introspect-singlestore.ts b/drizzle-kit/src/introspect-singlestore.ts index ee0ae5e0d..e39c0fe19 100644 --- a/drizzle-kit/src/introspect-singlestore.ts +++ b/drizzle-kit/src/introspect-singlestore.ts @@ -380,9 +380,7 @@ const column = ( if (lowered.startsWith('int')) { const isUnsigned = lowered.includes('unsigned'); const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); - let out = `${casing(name)}: int(${columnName}${ - isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' - })`; + let out = `${casing(name)}: int(${columnName}${isUnsigned ? '{ unsigned: true }' : ''})`; out += autoincrement ? `.autoincrement()` : ''; out += typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -393,10 +391,7 @@ const column = ( if (lowered.startsWith('tinyint')) { const isUnsigned = lowered.includes('unsigned'); const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); - // let out = `${name.camelCase()}: tinyint("${name}")`; - let out: string = `${casing(name)}: tinyint(${columnName}${ - isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' - })`; + let out: string = `${casing(name)}: tinyint(${columnName}${isUnsigned ? '{ unsigned: true }' : ''})`; out += autoincrement ? `.autoincrement()` : ''; out += typeof defaultValue !== 'undefined' ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -407,9 +402,7 @@ const column = ( if (lowered.startsWith('smallint')) { const isUnsigned = lowered.includes('unsigned'); const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); - let out = `${casing(name)}: smallint(${columnName}${ - isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' - })`; + let out = `${casing(name)}: smallint(${columnName}${isUnsigned ? '{ unsigned: true }' : ''})`; out += autoincrement ? `.autoincrement()` : ''; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` @@ -420,9 +413,7 @@ const column = ( if (lowered.startsWith('mediumint')) { const isUnsigned = lowered.includes('unsigned'); const columnName = dbColumnName({ name, casing: rawCasing, withMode: isUnsigned }); - let out = `${casing(name)}: mediumint(${columnName}${ - isUnsigned ? `${columnName.length > 0 ? ', ' : ''}{ unsigned: true }` : '' - })`; + let out = `${casing(name)}: mediumint(${columnName}${isUnsigned ? '{ unsigned: true }' : ''})`; out += autoincrement ? `.autoincrement()` : ''; out += defaultValue ? `.default(${mapColumnDefault(defaultValue, isExpression)})` From 0af1a1290f29608ff7c60dd5a71c13cda9d7e184 Mon Sep 17 00:00:00 2001 From: OleksiiKH0240 <homenko0240@gmail.com> Date: Tue, 28 Jan 2025 12:45:37 +0200 Subject: [PATCH 05/11] fix --- drizzle-seed/src/services/SeedService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drizzle-seed/src/services/SeedService.ts b/drizzle-seed/src/services/SeedService.ts index e68a939e0..22d92655c 100644 --- a/drizzle-seed/src/services/SeedService.ts +++ b/drizzle-seed/src/services/SeedService.ts @@ -411,7 +411,7 @@ export class SeedService { ) { orderedTablesNames.push(parent); } else { - leafTablesNames.push(parent); + leafTablesNames.push(...tablesInOutRelations[parent]!.requiredTableNames, parent); continue; } From 348e49632fc04fcd4e13de3ca99ba5443058bc1b Mon Sep 17 00:00:00 2001 From: Sukairo-02 <sreka9056@gmail.com> Date: Tue, 28 Jan 2025 15:32:45 +0200 Subject: [PATCH 06/11] Fixed SQLite onConflict clauses being overwritten instead of stacked, added related tests, removed unused import --- drizzle-orm/src/sqlite-core/db.ts | 2 +- drizzle-orm/src/sqlite-core/dialect.ts | 4 +- .../src/sqlite-core/query-builders/insert.ts | 15 +- .../tests/sqlite/better-sqlite.test.ts | 2 +- .../tests/sqlite/sqlite-common.ts | 176 ++++++++++++++++++ 5 files changed, 192 insertions(+), 7 deletions(-) diff --git a/drizzle-orm/src/sqlite-core/db.ts b/drizzle-orm/src/sqlite-core/db.ts index f8593c783..f5735155f 100644 --- a/drizzle-orm/src/sqlite-core/db.ts +++ b/drizzle-orm/src/sqlite-core/db.ts @@ -25,7 +25,7 @@ import { SQLiteCountBuilder } from './query-builders/count.ts'; import { RelationalQueryBuilder } from './query-builders/query.ts'; import { SQLiteRaw } from './query-builders/raw.ts'; import type { SelectedFields } from './query-builders/select.types.ts'; -import type { WithBuilder, WithSubqueryWithSelection } from './subquery.ts'; +import type { WithBuilder } from './subquery.ts'; import type { SQLiteViewBase } from './view-base.ts'; export class BaseSQLiteDatabase< diff --git a/drizzle-orm/src/sqlite-core/dialect.ts b/drizzle-orm/src/sqlite-core/dialect.ts index 8db4ee56b..6eae0dd93 100644 --- a/drizzle-orm/src/sqlite-core/dialect.ts +++ b/drizzle-orm/src/sqlite-core/dialect.ts @@ -487,7 +487,9 @@ export abstract class SQLiteDialect { ? sql` returning ${this.buildSelection(returning, { isSingleTable: true })}` : undefined; - const onConflictSql = onConflict ? sql` on conflict ${onConflict}` : undefined; + const onConflictSql = onConflict?.length + ? sql.join(onConflict) + : undefined; // if (isSingleValue && valuesSqlList.length === 0){ // return sql`insert into ${table} default values ${onConflictSql}${returningSql}`; diff --git a/drizzle-orm/src/sqlite-core/query-builders/insert.ts b/drizzle-orm/src/sqlite-core/query-builders/insert.ts index 2c26df8df..7609162c3 100644 --- a/drizzle-orm/src/sqlite-core/query-builders/insert.ts +++ b/drizzle-orm/src/sqlite-core/query-builders/insert.ts @@ -21,7 +21,7 @@ export interface SQLiteInsertConfig<TTable extends SQLiteTable = SQLiteTable> { table: TTable; values: Record<string, Param | SQL>[] | SQLiteInsertSelectQueryBuilder<TTable> | SQL; withList?: Subquery[]; - onConflict?: SQL; + onConflict?: SQL[]; returning?: SelectedFieldsOrdered; select?: boolean; } @@ -303,12 +303,14 @@ export class SQLiteInsertBase< * ``` */ onConflictDoNothing(config: { target?: IndexColumn | IndexColumn[]; where?: SQL } = {}): this { + if (!this.config.onConflict) this.config.onConflict = []; + if (config.target === undefined) { - this.config.onConflict = sql`do nothing`; + this.config.onConflict.push(sql` on conflict do nothing`); } else { const targetSql = Array.isArray(config.target) ? sql`${config.target}` : sql`${[config.target]}`; const whereSql = config.where ? sql` where ${config.where}` : sql``; - this.config.onConflict = sql`${targetSql} do nothing${whereSql}`; + this.config.onConflict.push(sql` on conflict ${targetSql} do nothing${whereSql}`); } return this; } @@ -348,12 +350,17 @@ export class SQLiteInsertBase< 'You cannot use both "where" and "targetWhere"/"setWhere" at the same time - "where" is deprecated, use "targetWhere" or "setWhere" instead.', ); } + + if (!this.config.onConflict) this.config.onConflict = []; + const whereSql = config.where ? sql` where ${config.where}` : undefined; const targetWhereSql = config.targetWhere ? sql` where ${config.targetWhere}` : undefined; const setWhereSql = config.setWhere ? sql` where ${config.setWhere}` : undefined; const targetSql = Array.isArray(config.target) ? sql`${config.target}` : sql`${[config.target]}`; const setSql = this.dialect.buildUpdateSet(this.config.table, mapUpdateSet(this.config.table, config.set)); - this.config.onConflict = sql`${targetSql}${targetWhereSql} do update set ${setSql}${whereSql}${setWhereSql}`; + this.config.onConflict.push( + sql` on conflict ${targetSql}${targetWhereSql} do update set ${setSql}${whereSql}${setWhereSql}`, + ); return this; } diff --git a/integration-tests/tests/sqlite/better-sqlite.test.ts b/integration-tests/tests/sqlite/better-sqlite.test.ts index 53feee15f..ed9a2f861 100644 --- a/integration-tests/tests/sqlite/better-sqlite.test.ts +++ b/integration-tests/tests/sqlite/better-sqlite.test.ts @@ -6,7 +6,7 @@ import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; import { skipTests } from '~/common'; import { anotherUsersMigratorTable, tests, usersMigratorTable } from './sqlite-common'; -const ENABLE_LOGGING = false; +const ENABLE_LOGGING = true; let db: BetterSQLite3Database; let client: Database.Database; diff --git a/integration-tests/tests/sqlite/sqlite-common.ts b/integration-tests/tests/sqlite/sqlite-common.ts index 2419b1cc2..1263e46ed 100644 --- a/integration-tests/tests/sqlite/sqlite-common.ts +++ b/integration-tests/tests/sqlite/sqlite-common.ts @@ -125,6 +125,14 @@ const pkExampleTable = sqliteTable('pk_example', { compositePk: primaryKey({ columns: [table.id, table.name] }), })); +const conflictChainExampleTable = sqliteTable('conflict_chain_example', { + id: integer('id').notNull().unique(), + name: text('name').notNull(), + email: text('email').notNull(), +}, (table) => ({ + compositePk: primaryKey({ columns: [table.id, table.name] }), +})); + const bigIntExample = sqliteTable('big_int_example', { id: integer('id').primaryKey(), name: text('name').notNull(), @@ -154,6 +162,7 @@ export function tests() { await db.run(sql`drop table if exists ${orders}`); await db.run(sql`drop table if exists ${bigIntExample}`); await db.run(sql`drop table if exists ${pkExampleTable}`); + await db.run(sql`drop table if exists ${conflictChainExampleTable}`); await db.run(sql`drop table if exists user_notifications_insert_into`); await db.run(sql`drop table if exists users_insert_into`); await db.run(sql`drop table if exists notifications_insert_into`); @@ -212,6 +221,14 @@ export function tests() { primary key (id, name) ) `); + await db.run(sql` + create table ${conflictChainExampleTable} ( + id integer not null unique, + name text not null, + email text not null, + primary key (id, name) + ) + `); await db.run(sql` create table ${bigIntExample} ( id integer primary key, @@ -2037,6 +2054,165 @@ export function tests() { expect(res).toEqual([{ id: 1, name: 'John', email: 'john1@example.com' }]); }); + test('insert with onConflict chained (.update -> .nothing)', async (ctx) => { + const { db } = ctx.sqlite; + + await db.insert(conflictChainExampleTable).values([{ id: 1, name: 'John', email: 'john@example.com' }, { + id: 2, + name: 'John Second', + email: '2john@example.com', + }]).run(); + + await db + .insert(conflictChainExampleTable) + .values([{ id: 1, name: 'John', email: 'john@example.com' }, { + id: 2, + name: 'Anthony', + email: 'idthief@example.com', + }]) + .onConflictDoUpdate({ + target: [conflictChainExampleTable.id, conflictChainExampleTable.name], + set: { email: 'john1@example.com' }, + }) + .onConflictDoNothing({ target: conflictChainExampleTable.id }) + .run(); + + const res = await db + .select({ + id: conflictChainExampleTable.id, + name: conflictChainExampleTable.name, + email: conflictChainExampleTable.email, + }) + .from(conflictChainExampleTable) + .orderBy(conflictChainExampleTable.id) + .all(); + + expect(res).toEqual([{ id: 1, name: 'John', email: 'john1@example.com' }, { + id: 2, + name: 'John Second', + email: '2john@example.com', + }]); + }); + + test('insert with onConflict chained (.nothing -> .update)', async (ctx) => { + const { db } = ctx.sqlite; + + await db.insert(conflictChainExampleTable).values([{ id: 1, name: 'John', email: 'john@example.com' }, { + id: 2, + name: 'John Second', + email: '2john@example.com', + }]).run(); + + await db + .insert(conflictChainExampleTable) + .values([{ id: 1, name: 'John', email: 'john@example.com' }, { + id: 2, + name: 'Anthony', + email: 'idthief@example.com', + }]) + .onConflictDoUpdate({ + target: [conflictChainExampleTable.id, conflictChainExampleTable.name], + set: { email: 'john1@example.com' }, + }) + .onConflictDoNothing({ target: conflictChainExampleTable.id }) + .run(); + + const res = await db + .select({ + id: conflictChainExampleTable.id, + name: conflictChainExampleTable.name, + email: conflictChainExampleTable.email, + }) + .from(conflictChainExampleTable) + .orderBy(conflictChainExampleTable.id) + .all(); + + expect(res).toEqual([{ id: 1, name: 'John', email: 'john1@example.com' }, { + id: 2, + name: 'John Second', + email: '2john@example.com', + }]); + }); + + test('insert with onConflict chained (.update -> .update)', async (ctx) => { + const { db } = ctx.sqlite; + + await db.insert(conflictChainExampleTable).values([{ id: 1, name: 'John', email: 'john@example.com' }, { + id: 2, + name: 'John Second', + email: '2john@example.com', + }]).run(); + + await db + .insert(conflictChainExampleTable) + .values([{ id: 1, name: 'John', email: 'john@example.com' }, { + id: 2, + name: 'Anthony', + email: 'idthief@example.com', + }]) + .onConflictDoUpdate({ + target: [conflictChainExampleTable.id, conflictChainExampleTable.name], + set: { email: 'john1@example.com' }, + }) + .onConflictDoUpdate({ target: conflictChainExampleTable.id, set: { email: 'john2@example.com' } }) + .run(); + + const res = await db + .select({ + id: conflictChainExampleTable.id, + name: conflictChainExampleTable.name, + email: conflictChainExampleTable.email, + }) + .from(conflictChainExampleTable) + .orderBy(conflictChainExampleTable.id) + .all(); + + expect(res).toEqual([{ id: 1, name: 'John', email: 'john1@example.com' }, { + id: 2, + name: 'John Second', + email: 'john2@example.com', + }]); + }); + + test('insert with onConflict chained (.nothing -> .nothing)', async (ctx) => { + const { db } = ctx.sqlite; + + await db.insert(conflictChainExampleTable).values([{ id: 1, name: 'John', email: 'john@example.com' }, { + id: 2, + name: 'John Second', + email: '2john@example.com', + }]).run(); + + await db + .insert(conflictChainExampleTable) + .values([{ id: 1, name: 'John', email: 'john@example.com' }, { + id: 2, + name: 'Anthony', + email: 'idthief@example.com', + }]) + .onConflictDoNothing({ + target: [conflictChainExampleTable.id, conflictChainExampleTable.name], + }) + .onConflictDoNothing({ target: conflictChainExampleTable.id }) + .run(); + + const res = await db + .select({ + id: conflictChainExampleTable.id, + name: conflictChainExampleTable.name, + email: conflictChainExampleTable.email, + }) + .from(conflictChainExampleTable) + .orderBy(conflictChainExampleTable.id) + .all(); + + expect(res).toEqual([{ id: 1, name: 'John', email: 'john@example.com' }, { + id: 2, + name: 'John Second', + email: '2john@example.com', + }]); + }); + test('insert undefined', async (ctx) => { const { db } = ctx.sqlite; From 2c677837c4c5efa556100fee4c0f8b422f5f74fb Mon Sep 17 00:00:00 2001 From: Sukairo-02 <sreka9056@gmail.com> Date: Tue, 28 Jan 2025 15:35:02 +0200 Subject: [PATCH 07/11] Reverted flag change --- integration-tests/tests/sqlite/better-sqlite.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/tests/sqlite/better-sqlite.test.ts b/integration-tests/tests/sqlite/better-sqlite.test.ts index ed9a2f861..53feee15f 100644 --- a/integration-tests/tests/sqlite/better-sqlite.test.ts +++ b/integration-tests/tests/sqlite/better-sqlite.test.ts @@ -6,7 +6,7 @@ import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; import { skipTests } from '~/common'; import { anotherUsersMigratorTable, tests, usersMigratorTable } from './sqlite-common'; -const ENABLE_LOGGING = true; +const ENABLE_LOGGING = false; let db: BetterSQLite3Database; let client: Database.Database; From 6b6aca742968291d50ac915a903656ff577917e6 Mon Sep 17 00:00:00 2001 From: Sukairo-02 <sreka9056@gmail.com> Date: Tue, 28 Jan 2025 20:19:47 +0200 Subject: [PATCH 08/11] Fixed types of joined views, fixed `aliasedTable()` not aliasing tables on type level, added `View` support to `aliasedTable()`, fixed sql builder prefixing aliased views and tables with their schema names --- drizzle-orm/src/alias.ts | 16 +- .../mysql-core/query-builders/select.types.ts | 2 +- .../pg-core/query-builders/select.types.ts | 2 +- drizzle-orm/src/sql/sql.ts | 4 +- .../type-tests/common/aliased-table.ts | 229 ++++++++++++++++++ 5 files changed, 247 insertions(+), 6 deletions(-) create mode 100644 drizzle-orm/type-tests/common/aliased-table.ts diff --git a/drizzle-orm/src/alias.ts b/drizzle-orm/src/alias.ts index 0711dc53d..eb2270f8e 100644 --- a/drizzle-orm/src/alias.ts +++ b/drizzle-orm/src/alias.ts @@ -1,9 +1,14 @@ import type { AnyColumn } from './column.ts'; import { Column } from './column.ts'; import { entityKind, is } from './entity.ts'; +import type { AnyMySqlTable, BuildAliasTable as BuildAliasMySqlTable, MySqlView } from './mysql-core/index.ts'; +import type { AnyPgTable, BuildAliasTable as BuildAliasPgTable, PgView } from './pg-core/index.ts'; import type { Relation } from './relations.ts'; import type { View } from './sql/sql.ts'; import { SQL, sql } from './sql/sql.ts'; +import type { BuildAliasTable as BuildAliasSQLiteTable } from './sqlite-core/index.ts'; +import type { AnySQLiteTable } from './sqlite-core/table.ts'; +import type { SQLiteView } from './sqlite-core/view.ts'; import { Table } from './table.ts'; import { ViewBaseConfig } from './view-common.ts'; @@ -88,8 +93,15 @@ export class RelationTableAliasProxyHandler<T extends Relation> implements Proxy } } -export function aliasedTable<T extends Table>(table: T, tableAlias: string): T { - return new Proxy(table, new TableAliasProxyHandler(tableAlias, false)); +export function aliasedTable<T extends Table | View, TAlias extends string>( + table: T, + tableAlias: TAlias, +): T extends AnyPgTable | PgView ? BuildAliasPgTable<T, TAlias> + : T extends AnySQLiteTable | SQLiteView ? BuildAliasSQLiteTable<T, TAlias> + : T extends AnyMySqlTable | MySqlView ? BuildAliasMySqlTable<T, TAlias> + : never +{ + return new Proxy(table, new TableAliasProxyHandler(tableAlias, false)) as any; } export function aliasedRelation<T extends Relation>(relation: T, tableAlias: string): T { diff --git a/drizzle-orm/src/mysql-core/query-builders/select.types.ts b/drizzle-orm/src/mysql-core/query-builders/select.types.ts index 78b6f91a6..4b0f97d3a 100644 --- a/drizzle-orm/src/mysql-core/query-builders/select.types.ts +++ b/drizzle-orm/src/mysql-core/query-builders/select.types.ts @@ -97,7 +97,7 @@ export type MySqlJoin< T['_']['selection'], TJoinedName, TJoinedTable extends MySqlTable ? TJoinedTable['_']['columns'] - : TJoinedTable extends Subquery ? Assume<TJoinedTable['_']['selectedFields'], SelectedFields> + : TJoinedTable extends Subquery | View ? Assume<TJoinedTable['_']['selectedFields'], SelectedFields> : never, T['_']['selectMode'] >, diff --git a/drizzle-orm/src/pg-core/query-builders/select.types.ts b/drizzle-orm/src/pg-core/query-builders/select.types.ts index 87f21e526..b887c1ebd 100644 --- a/drizzle-orm/src/pg-core/query-builders/select.types.ts +++ b/drizzle-orm/src/pg-core/query-builders/select.types.ts @@ -98,7 +98,7 @@ export type PgSelectJoin< T['_']['selection'], TJoinedName, TJoinedTable extends Table ? TJoinedTable['_']['columns'] - : TJoinedTable extends Subquery ? Assume<TJoinedTable['_']['selectedFields'], SelectedFields> + : TJoinedTable extends Subquery | View ? Assume<TJoinedTable['_']['selectedFields'], SelectedFields> : never, T['_']['selectMode'] >, diff --git a/drizzle-orm/src/sql/sql.ts b/drizzle-orm/src/sql/sql.ts index 50d4c1557..ec4feb20c 100644 --- a/drizzle-orm/src/sql/sql.ts +++ b/drizzle-orm/src/sql/sql.ts @@ -181,7 +181,7 @@ export class SQL<T = unknown> implements SQLWrapper { const schemaName = chunk[Table.Symbol.Schema]; const tableName = chunk[Table.Symbol.Name]; return { - sql: schemaName === undefined + sql: schemaName === undefined || chunk[IsAlias] ? escapeName(tableName) : escapeName(schemaName) + '.' + escapeName(tableName), params: [], @@ -208,7 +208,7 @@ export class SQL<T = unknown> implements SQLWrapper { const schemaName = chunk[ViewBaseConfig].schema; const viewName = chunk[ViewBaseConfig].name; return { - sql: schemaName === undefined + sql: schemaName === undefined || chunk[ViewBaseConfig].isAlias ? escapeName(viewName) : escapeName(schemaName) + '.' + escapeName(viewName), params: [], diff --git a/drizzle-orm/type-tests/common/aliased-table.ts b/drizzle-orm/type-tests/common/aliased-table.ts new file mode 100644 index 000000000..59fc7516f --- /dev/null +++ b/drizzle-orm/type-tests/common/aliased-table.ts @@ -0,0 +1,229 @@ +import { type Equal, Expect } from 'type-tests/utils.ts'; +import { aliasedTable, eq } from '~/index.ts'; +import { drizzle as sqlited } from '~/libsql/index.ts'; +import { mysqlView } from '~/mysql-core/view.ts'; +import { drizzle as mysqld } from '~/mysql2/index.ts'; +import { pgView } from '~/pg-core/view.ts'; +import { drizzle as pgd } from '~/postgres-js/index.ts'; +import { sqliteView } from '~/sqlite-core/view.ts'; +import { users as mysqlUsers } from '../mysql/tables.ts'; +import { users as pgUsers } from '../pg/tables.ts'; +import { users as sqliteUsers } from '../sqlite/tables.ts'; + +const pg = pgd.mock(); +const sqlite = sqlited.mock(); +const mysql = mysqld.mock(); + +const pgvUsers = pgView('users_view').as((qb) => qb.select().from(pgUsers)); +const sqlitevUsers = sqliteView('users_view').as((qb) => qb.select().from(sqliteUsers)); +const mysqlvUsers = mysqlView('users_view').as((qb) => qb.select().from(mysqlUsers)); + +const pgAlias = aliasedTable(pgUsers, 'usersAlias'); +const sqliteAlias = aliasedTable(sqliteUsers, 'usersAlias'); +const mysqlAlias = aliasedTable(mysqlUsers, 'usersAlias'); + +const pgvAlias = aliasedTable(pgvUsers, 'usersvAlias'); +const sqlitevAlias = aliasedTable(sqlitevUsers, 'usersvAlias'); +const mysqlvAlias = aliasedTable(mysqlvUsers, 'usersvAlias'); + +const pgRes = await pg.select().from(pgUsers).leftJoin(pgAlias, eq(pgAlias.id, pgUsers.id)); +const sqliteRes = await sqlite.select().from(sqliteUsers).leftJoin(sqliteAlias, eq(sqliteAlias.id, sqliteUsers.id)); +const mysqlRes = await mysql.select().from(mysqlUsers).leftJoin(mysqlAlias, eq(mysqlAlias.id, mysqlUsers.id)); + +const pgvRes = await pg.select().from(pgUsers).leftJoin(pgvAlias, eq(pgvAlias.id, pgUsers.id)); +const sqlitevRes = await sqlite.select().from(sqliteUsers).leftJoin(sqlitevAlias, eq(sqlitevAlias.id, sqliteUsers.id)); +const mysqlvRes = await mysql.select().from(mysqlUsers).leftJoin(mysqlvAlias, eq(mysqlvAlias.id, mysqlUsers.id)); + +Expect< + Equal<typeof pgRes, { + users_table: { + id: number; + uuid: string; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + arrayCol: string[]; + }; + usersAlias: { + id: number; + uuid: string; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + arrayCol: string[]; + } | null; + }[]> +>; + +Expect< + Equal<typeof sqliteRes, { + users_table: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number | null; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + name: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }; + usersAlias: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number | null; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + name: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + } | null; + }[]> +>; + +Expect< + Equal<typeof mysqlRes, { + users_table: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }; + usersAlias: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + } | null; + }[]> +>; + +Expect< + Equal<typeof pgvRes, { + users_table: { + id: number; + uuid: string; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + arrayCol: string[]; + }; + usersvAlias: { + id: number; + uuid: string; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + arrayCol: string[]; + } | null; + }[]> +>; + +Expect< + Equal<typeof sqlitevRes, { + users_table: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number | null; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + name: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }; + usersvAlias: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number | null; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + name: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + } | null; + }[]> +>; + +Expect< + Equal<typeof mysqlvRes, { + users_table: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }; + usersvAlias: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + } | null; + }[]> +>; From a42bb0391586875cb7e62d8e49bed70164a262d0 Mon Sep 17 00:00:00 2001 From: Sukairo-02 <sreka9056@gmail.com> Date: Wed, 29 Jan 2025 11:42:22 +0200 Subject: [PATCH 09/11] Reverted `aliasedTable` type improvements in favor of dialect-specific `alias` functions --- drizzle-orm/src/alias.ts | 15 +++------------ drizzle-orm/type-tests/common/aliased-table.ts | 17 ++++++++++------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/drizzle-orm/src/alias.ts b/drizzle-orm/src/alias.ts index eb2270f8e..21e802a1c 100644 --- a/drizzle-orm/src/alias.ts +++ b/drizzle-orm/src/alias.ts @@ -1,14 +1,9 @@ import type { AnyColumn } from './column.ts'; import { Column } from './column.ts'; import { entityKind, is } from './entity.ts'; -import type { AnyMySqlTable, BuildAliasTable as BuildAliasMySqlTable, MySqlView } from './mysql-core/index.ts'; -import type { AnyPgTable, BuildAliasTable as BuildAliasPgTable, PgView } from './pg-core/index.ts'; import type { Relation } from './relations.ts'; import type { View } from './sql/sql.ts'; import { SQL, sql } from './sql/sql.ts'; -import type { BuildAliasTable as BuildAliasSQLiteTable } from './sqlite-core/index.ts'; -import type { AnySQLiteTable } from './sqlite-core/table.ts'; -import type { SQLiteView } from './sqlite-core/view.ts'; import { Table } from './table.ts'; import { ViewBaseConfig } from './view-common.ts'; @@ -93,14 +88,10 @@ export class RelationTableAliasProxyHandler<T extends Relation> implements Proxy } } -export function aliasedTable<T extends Table | View, TAlias extends string>( +export function aliasedTable<T extends Table | View>( table: T, - tableAlias: TAlias, -): T extends AnyPgTable | PgView ? BuildAliasPgTable<T, TAlias> - : T extends AnySQLiteTable | SQLiteView ? BuildAliasSQLiteTable<T, TAlias> - : T extends AnyMySqlTable | MySqlView ? BuildAliasMySqlTable<T, TAlias> - : never -{ + tableAlias: string, +): T { return new Proxy(table, new TableAliasProxyHandler(tableAlias, false)) as any; } diff --git a/drizzle-orm/type-tests/common/aliased-table.ts b/drizzle-orm/type-tests/common/aliased-table.ts index 59fc7516f..9c2be8c5f 100644 --- a/drizzle-orm/type-tests/common/aliased-table.ts +++ b/drizzle-orm/type-tests/common/aliased-table.ts @@ -1,10 +1,13 @@ import { type Equal, Expect } from 'type-tests/utils.ts'; -import { aliasedTable, eq } from '~/index.ts'; +import { eq } from '~/index.ts'; import { drizzle as sqlited } from '~/libsql/index.ts'; +import { alias as mysqlAliasFn } from '~/mysql-core/alias.ts'; import { mysqlView } from '~/mysql-core/view.ts'; import { drizzle as mysqld } from '~/mysql2/index.ts'; +import { alias as pgAliasFn } from '~/pg-core/alias.ts'; import { pgView } from '~/pg-core/view.ts'; import { drizzle as pgd } from '~/postgres-js/index.ts'; +import { alias as sqliteAliasFn } from '~/sqlite-core/alias.ts'; import { sqliteView } from '~/sqlite-core/view.ts'; import { users as mysqlUsers } from '../mysql/tables.ts'; import { users as pgUsers } from '../pg/tables.ts'; @@ -18,13 +21,13 @@ const pgvUsers = pgView('users_view').as((qb) => qb.select().from(pgUsers)); const sqlitevUsers = sqliteView('users_view').as((qb) => qb.select().from(sqliteUsers)); const mysqlvUsers = mysqlView('users_view').as((qb) => qb.select().from(mysqlUsers)); -const pgAlias = aliasedTable(pgUsers, 'usersAlias'); -const sqliteAlias = aliasedTable(sqliteUsers, 'usersAlias'); -const mysqlAlias = aliasedTable(mysqlUsers, 'usersAlias'); +const pgAlias = pgAliasFn(pgUsers, 'usersAlias'); +const sqliteAlias = sqliteAliasFn(sqliteUsers, 'usersAlias'); +const mysqlAlias = mysqlAliasFn(mysqlUsers, 'usersAlias'); -const pgvAlias = aliasedTable(pgvUsers, 'usersvAlias'); -const sqlitevAlias = aliasedTable(sqlitevUsers, 'usersvAlias'); -const mysqlvAlias = aliasedTable(mysqlvUsers, 'usersvAlias'); +const pgvAlias = pgAliasFn(pgvUsers, 'usersvAlias'); +const sqlitevAlias = sqliteAliasFn(sqlitevUsers, 'usersvAlias'); +const mysqlvAlias = mysqlAliasFn(mysqlvUsers, 'usersvAlias'); const pgRes = await pg.select().from(pgUsers).leftJoin(pgAlias, eq(pgAlias.id, pgUsers.id)); const sqliteRes = await sqlite.select().from(sqliteUsers).leftJoin(sqliteAlias, eq(sqliteAlias.id, sqliteUsers.id)); From 4074110f311e1b39ed4e67457f94e5be1f22a00c Mon Sep 17 00:00:00 2001 From: OleksiiKH0240 <homenko0240@gmail.com> Date: Wed, 29 Jan 2025 12:22:01 +0200 Subject: [PATCH 10/11] 0.3.1 --- changelogs/drizzle-seed/0.3.1.md | 38 ++++++++++++++++++++++++++++++++ drizzle-seed/package.json | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 changelogs/drizzle-seed/0.3.1.md diff --git a/changelogs/drizzle-seed/0.3.1.md b/changelogs/drizzle-seed/0.3.1.md new file mode 100644 index 000000000..5e814a468 --- /dev/null +++ b/changelogs/drizzle-seed/0.3.1.md @@ -0,0 +1,38 @@ +## Bug fixes + +- Combining a reference in a table schema (foreign key constraint) with a one-to-many relation for the same two tables defined in the constraint causes the seeder to duplicate these relations and enter an infinite loop. + +Example: + +```ts +// schema.ts +import { integer, pgTable, text } from "drizzle-orm/pg-core"; +import { relations } from "drizzle-orm/relations"; + +export const users = pgTable("users", { + id: integer().primaryKey(), + name: text(), + email: text(), +}); + +export const posts = pgTable("posts", { + id: integer().primaryKey(), + content: text(), + userId: integer().references(() => users.id), +}); + +export const postsRelation = relations(posts, ({ one }) => ({ + user: one(users, { + fields: [posts.userId], + references: [users.id], + }), +})); +``` + +Now, seeding with the schema above will trigger a warning. + +``` +You are providing a one-to-many relation between the 'users' and 'posts' tables, +while the 'posts' table object already has foreign key constraint in the schema referencing 'users' table. +In this case, the foreign key constraint will be used. +``` diff --git a/drizzle-seed/package.json b/drizzle-seed/package.json index a9287eb28..7efa03353 100644 --- a/drizzle-seed/package.json +++ b/drizzle-seed/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-seed", - "version": "0.3.0", + "version": "0.3.1", "main": "index.js", "type": "module", "scripts": { From 5ffd0bdfcf2f60eecbc85f0766c7bd23620d97a1 Mon Sep 17 00:00:00 2001 From: AndriiSherman <andreysherman11@gmail.com> Date: Wed, 29 Jan 2025 17:17:04 +0200 Subject: [PATCH 11/11] Bump versions --- changelogs/drizzle-kit/0.30.4.md | 2 ++ changelogs/drizzle-orm/0.39.1.md | 3 +++ drizzle-kit/package.json | 2 +- drizzle-orm/package.json | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changelogs/drizzle-kit/0.30.4.md create mode 100644 changelogs/drizzle-orm/0.39.1.md diff --git a/changelogs/drizzle-kit/0.30.4.md b/changelogs/drizzle-kit/0.30.4.md new file mode 100644 index 000000000..005c1a9a8 --- /dev/null +++ b/changelogs/drizzle-kit/0.30.4.md @@ -0,0 +1,2 @@ +- Fix bug that generates incorrect syntax when introspect in mysql +- Fix a bug that caused incorrect syntax output when introspect in unsigned columns \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.39.1.md b/changelogs/drizzle-orm/0.39.1.md new file mode 100644 index 000000000..012e4480e --- /dev/null +++ b/changelogs/drizzle-orm/0.39.1.md @@ -0,0 +1,3 @@ +- Fixed SQLite onConflict clauses being overwritten instead of stacked - [#2276](https://github.com/drizzle-team/drizzle-orm/issues/2276) +- Added view support to `aliasedTable()` +- Fixed sql builder prefixing aliased views and tables with their schema \ No newline at end of file diff --git a/drizzle-kit/package.json b/drizzle-kit/package.json index 9d4dd3824..5fe46a455 100644 --- a/drizzle-kit/package.json +++ b/drizzle-kit/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-kit", - "version": "0.30.3", + "version": "0.30.4", "homepage": "https://orm.drizzle.team", "keywords": [ "drizzle", diff --git a/drizzle-orm/package.json b/drizzle-orm/package.json index e376281f8..9209685a2 100644 --- a/drizzle-orm/package.json +++ b/drizzle-orm/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-orm", - "version": "0.39.0", + "version": "0.39.1", "description": "Drizzle ORM package for SQL databases", "type": "module", "scripts": {