diff --git a/drizzle-kit/src/serializer/mysqlSchema.ts b/drizzle-kit/src/serializer/mysqlSchema.ts index 3a6fb9179..ad47e49ec 100644 --- a/drizzle-kit/src/serializer/mysqlSchema.ts +++ b/drizzle-kit/src/serializer/mysqlSchema.ts @@ -9,6 +9,7 @@ const index = object({ using: enumType(['btree', 'hash']).optional(), algorithm: enumType(['default', 'inplace', 'copy']).optional(), lock: enumType(['default', 'none', 'shared', 'exclusive']).optional(), + raw: string().optional(), }).strict(); const fk = object({ @@ -219,15 +220,24 @@ export type CheckConstraint = TypeOf; export type View = TypeOf; export type ViewSquashed = TypeOf; +/** + * encode from `;` to `%3B` to avoid split error + */ +export const encodeRaw = (raw?: string) => raw?.replaceAll(';', '%3B'); + +/** + * decode from `%3B` to `;` to recover original value + */ +export const decodeRaw = (raw?: string) => raw?.replaceAll('%3B', ';'); + export const MySqlSquasher = { squashIdx: (idx: Index) => { index.parse(idx); - return `${idx.name};${idx.columns.join(',')};${idx.isUnique};${idx.using ?? ''};${idx.algorithm ?? ''};${ - idx.lock ?? '' - }`; + return `${idx.name};${idx.columns.join(',')};${idx.isUnique};${idx.using ?? ''};${idx.algorithm ?? ''};${idx.lock ?? '' + };${idx.raw ? encodeRaw(idx.raw) : ''}`; }, unsquashIdx: (input: string): Index => { - const [name, columnsString, isUnique, using, algorithm, lock] = input.split(';'); + const [name, columnsString, isUnique, using, algorithm, lock, raw] = input.split(';'); const destructed = { name, columns: columnsString.split(','), @@ -235,6 +245,7 @@ export const MySqlSquasher = { using: using ? using : undefined, algorithm: algorithm ? algorithm : undefined, lock: lock ? lock : undefined, + raw: raw ? raw : undefined, }; return index.parse(destructed); }, @@ -253,9 +264,8 @@ export const MySqlSquasher = { return { name, columns: columns.split(',') }; }, squashFK: (fk: ForeignKey) => { - return `${fk.name};${fk.tableFrom};${fk.columnsFrom.join(',')};${fk.tableTo};${fk.columnsTo.join(',')};${ - fk.onUpdate ?? '' - };${fk.onDelete ?? ''}`; + return `${fk.name};${fk.tableFrom};${fk.columnsFrom.join(',')};${fk.tableTo};${fk.columnsTo.join(',')};${fk.onUpdate ?? '' + };${fk.onDelete ?? ''}`; }, unsquashFK: (input: string): ForeignKey => { const [ diff --git a/drizzle-kit/src/serializer/mysqlSerializer.ts b/drizzle-kit/src/serializer/mysqlSerializer.ts index aaa1acb82..547cdb070 100644 --- a/drizzle-kit/src/serializer/mysqlSerializer.ts +++ b/drizzle-kit/src/serializer/mysqlSerializer.ts @@ -97,8 +97,8 @@ export const generateMySqlSnapshot = ( as: is(generated.as, SQL) ? dialect.sqlToQuery(generated.as as SQL).sql : typeof generated.as === 'function' - ? dialect.sqlToQuery(generated.as() as SQL).sql - : (generated.as as any), + ? dialect.sqlToQuery(generated.as() as SQL).sql + : (generated.as as any), type: generated.mode ?? 'stored', } : undefined, @@ -115,24 +115,19 @@ export const generateMySqlSnapshot = ( const existingUnique = uniqueConstraintObject[column.uniqueName!]; if (typeof existingUnique !== 'undefined') { console.log( - `\n${ - withStyle.errorWarning(`We\'ve found duplicated unique constraint names in ${ - chalk.underline.blue( - tableName, - ) + `\n${withStyle.errorWarning(`We\'ve found duplicated unique constraint names in ${chalk.underline.blue( + tableName, + ) } table. - The unique constraint ${ - chalk.underline.blue( - column.uniqueName, - ) - } on the ${ - chalk.underline.blue( - name, - ) - } column is confilcting with a unique constraint name already defined for ${ - chalk.underline.blue( - existingUnique.columns.join(','), - ) + The unique constraint ${chalk.underline.blue( + column.uniqueName, + ) + } on the ${chalk.underline.blue( + name, + ) + } column is confilcting with a unique constraint name already defined for ${chalk.underline.blue( + existingUnique.columns.join(','), + ) } columns\n`) }`, ); @@ -160,12 +155,11 @@ export const generateMySqlSnapshot = ( sqlTypeLowered.startsWith('datetime') || sqlTypeLowered.startsWith('timestamp') ) { - columnToSet.default = `'${ - column.default - .toISOString() - .replace('T', ' ') - .slice(0, 23) - }'`; + columnToSet.default = `'${column.default + .toISOString() + .replace('T', ' ') + .slice(0, 23) + }'`; } } else { columnToSet.default = column.default; @@ -209,26 +203,21 @@ export const generateMySqlSnapshot = ( const existingUnique = uniqueConstraintObject[name]; if (typeof existingUnique !== 'undefined') { console.log( - `\n${ - withStyle.errorWarning( - `We\'ve found duplicated unique constraint names in ${ - chalk.underline.blue( - tableName, - ) - } table. \nThe unique constraint ${ - chalk.underline.blue( - name, - ) - } on the ${ - chalk.underline.blue( - columnNames.join(','), - ) - } columns is confilcting with a unique constraint name already defined for ${ - chalk.underline.blue( - existingUnique.columns.join(','), - ) - } columns\n`, + `\n${withStyle.errorWarning( + `We\'ve found duplicated unique constraint names in ${chalk.underline.blue( + tableName, + ) + } table. \nThe unique constraint ${chalk.underline.blue( + name, + ) + } on the ${chalk.underline.blue( + columnNames.join(','), ) + } columns is confilcting with a unique constraint name already defined for ${chalk.underline.blue( + existingUnique.columns.join(','), + ) + } columns\n`, + ) }`, ); process.exit(1); @@ -314,26 +303,21 @@ export const generateMySqlSnapshot = ( if (value.config.unique) { if (typeof uniqueConstraintObject[name] !== 'undefined') { console.log( - `\n${ - withStyle.errorWarning( - `We\'ve found duplicated unique constraint names in ${ - chalk.underline.blue( - tableName, - ) - } table. \nThe unique index ${ - chalk.underline.blue( - name, - ) - } on the ${ - chalk.underline.blue( - indexColumns.join(','), - ) - } columns is confilcting with a unique constraint name already defined for ${ - chalk.underline.blue( - uniqueConstraintObject[name].columns.join(','), - ) - } columns\n`, + `\n${withStyle.errorWarning( + `We\'ve found duplicated unique constraint names in ${chalk.underline.blue( + tableName, + ) + } table. \nThe unique index ${chalk.underline.blue( + name, + ) + } on the ${chalk.underline.blue( + indexColumns.join(','), ) + } columns is confilcting with a unique constraint name already defined for ${chalk.underline.blue( + uniqueConstraintObject[name].columns.join(','), + ) + } columns\n`, + ) }`, ); process.exit(1); @@ -341,19 +325,16 @@ export const generateMySqlSnapshot = ( } else { if (typeof foreignKeysObject[name] !== 'undefined') { console.log( - `\n${ - withStyle.errorWarning( - `In MySQL, when creating a foreign key, an index is automatically generated with the same name as the foreign key constraint.\n\nWe have encountered a collision between the index name on columns ${ - chalk.underline.blue( - indexColumns.join(','), - ) - } and the foreign key on columns ${ - chalk.underline.blue( - foreignKeysObject[name].columnsFrom.join(','), - ) - }. Please change either the index name or the foreign key name. For more information, please refer to https://dev.mysql.com/doc/refman/8.0/en/constraint-foreign-key.html\n - `, + `\n${withStyle.errorWarning( + `In MySQL, when creating a foreign key, an index is automatically generated with the same name as the foreign key constraint.\n\nWe have encountered a collision between the index name on columns ${chalk.underline.blue( + indexColumns.join(','), ) + } and the foreign key on columns ${chalk.underline.blue( + foreignKeysObject[name].columnsFrom.join(','), + ) + }. Please change either the index name or the foreign key name. For more information, please refer to https://dev.mysql.com/doc/refman/8.0/en/constraint-foreign-key.html\n + `, + ) }`, ); process.exit(1); @@ -367,6 +348,7 @@ export const generateMySqlSnapshot = ( using: value.config.using, algorithm: value.config.algorythm, lock: value.config.lock, + raw: value.config.raw }; }); @@ -376,18 +358,15 @@ export const generateMySqlSnapshot = ( if (typeof checksInTable[tableName] !== 'undefined') { if (checksInTable[tableName].includes(check.name)) { console.log( - `\n${ - withStyle.errorWarning( - `We\'ve found duplicated check constraint name in ${ - chalk.underline.blue( - tableName, - ) - }. Please rename your check constraint in the ${ - chalk.underline.blue( - tableName, - ) - } table`, + `\n${withStyle.errorWarning( + `We\'ve found duplicated check constraint name in ${chalk.underline.blue( + tableName, + ) + }. Please rename your check constraint in the ${chalk.underline.blue( + tableName, ) + } table`, + ) }`, ); process.exit(1); @@ -434,14 +413,12 @@ export const generateMySqlSnapshot = ( const existingView = resultViews[name]; if (typeof existingView !== 'undefined') { console.log( - `\n${ - withStyle.errorWarning( - `We\'ve found duplicated view name across ${ - chalk.underline.blue( - schema ?? 'public', - ) - } schema. Please rename your view`, + `\n${withStyle.errorWarning( + `We\'ve found duplicated view name across ${chalk.underline.blue( + schema ?? 'public', ) + } schema. Please rename your view`, + ) }`, ); process.exit(1); @@ -473,8 +450,8 @@ export const generateMySqlSnapshot = ( as: is(generated.as, SQL) ? dialect.sqlToQuery(generated.as as SQL).sql : typeof generated.as === 'function' - ? dialect.sqlToQuery(generated.as() as SQL).sql - : (generated.as as any), + ? dialect.sqlToQuery(generated.as() as SQL).sql + : (generated.as as any), type: generated.mode ?? 'stored', } : undefined, @@ -496,12 +473,11 @@ export const generateMySqlSnapshot = ( sqlTypeLowered.startsWith('datetime') || sqlTypeLowered.startsWith('timestamp') ) { - columnToSet.default = `'${ - column.default - .toISOString() - .replace('T', ' ') - .slice(0, 23) - }'`; + columnToSet.default = `'${column.default + .toISOString() + .replace('T', ' ') + .slice(0, 23) + }'`; } } else { columnToSet.default = column.default; @@ -676,11 +652,11 @@ export const fromDatabase = async ( default: columnDefault === null || columnDefault === undefined ? undefined : /^-?[\d.]+(?:e-?\d+)?$/.test(columnDefault) - && !['decimal', 'char', 'varchar'].some((type) => columnType.startsWith(type)) - ? Number(columnDefault) - : isDefaultAnExpression - ? clearDefaults(columnDefault, collation) - : `'${escapeSingleQuotes(columnDefault)}'`, + && !['decimal', 'char', 'varchar'].some((type) => columnType.startsWith(type)) + ? Number(columnDefault) + : isDefaultAnExpression + ? clearDefaults(columnDefault, collation) + : `'${escapeSingleQuotes(columnDefault)}'`, autoincrement: isAutoincrement, name: columnName, type: changedType, @@ -708,7 +684,7 @@ export const fromDatabase = async ( } else { if ( typeof internals!.tables![tableName]!.columns[columnName] - === 'undefined' + === 'undefined' ) { internals!.tables![tableName]!.columns[columnName] = { isDefaultAnExpression: true, diff --git a/drizzle-kit/src/sqlgenerator.ts b/drizzle-kit/src/sqlgenerator.ts index 26adaf531..98de5e7b4 100644 --- a/drizzle-kit/src/sqlgenerator.ts +++ b/drizzle-kit/src/sqlgenerator.ts @@ -83,7 +83,7 @@ import { JsonStatement, } from './jsonStatements'; import { Dialect } from './schemaValidator'; -import { MySqlSquasher } from './serializer/mysqlSchema'; +import { MySqlSquasher, decodeRaw } from './serializer/mysqlSchema'; import { PgSquasher, policy } from './serializer/pgSchema'; import { SingleStoreSquasher } from './serializer/singlestoreSchema'; import { SQLiteSchemaSquashed, SQLiteSquasher } from './serializer/sqliteSchema'; @@ -160,13 +160,11 @@ class PgCreateRoleConvertor extends Convertor { return statement.type === 'create_role' && dialect === 'postgresql'; } override convert(statement: JsonCreateRoleStatement): string | string[] { - return `CREATE ROLE "${statement.name}"${ - statement.values.createDb || statement.values.createRole || !statement.values.inherit - ? ` WITH${statement.values.createDb ? ' CREATEDB' : ''}${statement.values.createRole ? ' CREATEROLE' : ''}${ - statement.values.inherit ? '' : ' NOINHERIT' - }` - : '' - };`; + return `CREATE ROLE "${statement.name}"${statement.values.createDb || statement.values.createRole || !statement.values.inherit + ? ` WITH${statement.values.createDb ? ' CREATEDB' : ''}${statement.values.createRole ? ' CREATEROLE' : ''}${statement.values.inherit ? '' : ' NOINHERIT' + }` + : '' + };`; } } @@ -193,9 +191,8 @@ class PgAlterRoleConvertor extends Convertor { return statement.type === 'alter_role' && dialect === 'postgresql'; } override convert(statement: JsonAlterRoleStatement): string | string[] { - return `ALTER ROLE "${statement.name}"${` WITH${statement.values.createDb ? ' CREATEDB' : ' NOCREATEDB'}${ - statement.values.createRole ? ' CREATEROLE' : ' NOCREATEROLE' - }${statement.values.inherit ? ' INHERIT' : ' NOINHERIT'}`};`; + return `ALTER ROLE "${statement.name}"${` WITH${statement.values.createDb ? ' CREATEDB' : ' NOCREATEDB'}${statement.values.createRole ? ' CREATEROLE' : ' NOCREATEROLE' + }${statement.values.inherit ? ' INHERIT' : ' NOINHERIT'}`};`; } } @@ -271,14 +268,14 @@ class PgAlterPolicyConvertor extends Convertor { const usingPart = newPolicy.using ? ` USING (${newPolicy.using})` : oldPolicy.using - ? ` USING (${oldPolicy.using})` - : ''; + ? ` USING (${oldPolicy.using})` + : ''; const withCheckPart = newPolicy.withCheck ? ` WITH CHECK (${newPolicy.withCheck})` : oldPolicy.withCheck - ? ` WITH CHECK (${oldPolicy.withCheck})` - : ''; + ? ` WITH CHECK (${oldPolicy.withCheck})` + : ''; return `ALTER POLICY "${oldPolicy.name}" ON ${tableNameWithSchema} TO ${newPolicy.to}${usingPart}${withCheckPart};`; } @@ -336,14 +333,14 @@ class PgAlterIndPolicyConvertor extends Convertor { const usingPart = newPolicy.using ? ` USING (${newPolicy.using})` : oldPolicy.using - ? ` USING (${oldPolicy.using})` - : ''; + ? ` USING (${oldPolicy.using})` + : ''; const withCheckPart = newPolicy.withCheck ? ` WITH CHECK (${newPolicy.withCheck})` : oldPolicy.withCheck - ? ` WITH CHECK (${oldPolicy.withCheck})` - : ''; + ? ` WITH CHECK (${oldPolicy.withCheck})` + : ''; return `ALTER POLICY "${oldPolicy.name}" ON ${oldPolicy.on} TO ${newPolicy.to}${usingPart}${withCheckPart};`; } @@ -419,26 +416,20 @@ class PgCreateTableConvertor extends Convertor { : `"${unsquashedIdentity?.name}"`; const identity = unsquashedIdentity - ? ` GENERATED ${ - unsquashedIdentity.type === 'always' ? 'ALWAYS' : 'BY DEFAULT' - } AS IDENTITY (sequence name ${identityWithSchema}${ - unsquashedIdentity.increment - ? ` INCREMENT BY ${unsquashedIdentity.increment}` - : '' - }${ - unsquashedIdentity.minValue - ? ` MINVALUE ${unsquashedIdentity.minValue}` - : '' - }${ - unsquashedIdentity.maxValue - ? ` MAXVALUE ${unsquashedIdentity.maxValue}` - : '' - }${ - unsquashedIdentity.startWith - ? ` START WITH ${unsquashedIdentity.startWith}` - : '' - }${unsquashedIdentity.cache ? ` CACHE ${unsquashedIdentity.cache}` : ''}${ - unsquashedIdentity.cycle ? ` CYCLE` : '' + ? ` GENERATED ${unsquashedIdentity.type === 'always' ? 'ALWAYS' : 'BY DEFAULT' + } AS IDENTITY (sequence name ${identityWithSchema}${unsquashedIdentity.increment + ? ` INCREMENT BY ${unsquashedIdentity.increment}` + : '' + }${unsquashedIdentity.minValue + ? ` MINVALUE ${unsquashedIdentity.minValue}` + : '' + }${unsquashedIdentity.maxValue + ? ` MAXVALUE ${unsquashedIdentity.maxValue}` + : '' + }${unsquashedIdentity.startWith + ? ` START WITH ${unsquashedIdentity.startWith}` + : '' + }${unsquashedIdentity.cache ? ` CACHE ${unsquashedIdentity.cache}` : ''}${unsquashedIdentity.cycle ? ` CYCLE` : '' })` : ''; @@ -461,9 +452,8 @@ class PgCreateTableConvertor extends Convertor { for (const uniqueConstraint of uniqueConstraints) { statement += ',\n'; const unsquashedUnique = PgSquasher.unsquashUnique(uniqueConstraint); - statement += `\tCONSTRAINT "${unsquashedUnique.name}" UNIQUE${ - unsquashedUnique.nullsNotDistinct ? ' NULLS NOT DISTINCT' : '' - }(\"${unsquashedUnique.columns.join(`","`)}\")`; + statement += `\tCONSTRAINT "${unsquashedUnique.name}" UNIQUE${unsquashedUnique.nullsNotDistinct ? ' NULLS NOT DISTINCT' : '' + }(\"${unsquashedUnique.columns.join(`","`)}\")`; // statement += `\n`; } } @@ -549,7 +539,7 @@ class MySqlCreateTableConvertor extends Convertor { .map((it) => { return internals?.indexes ? internals?.indexes[unsquashedUnique.name]?.columns[it] - ?.isExpression + ?.isExpression ? it : `\`${it}\`` : `\`${it}\``; @@ -633,7 +623,7 @@ class SingleStoreCreateTableConvertor extends Convertor { .map((it) => { return internals?.indexes ? internals?.indexes[unsquashedUnique.name]?.columns[it] - ?.isExpression + ?.isExpression ? it : `\`${it}\`` : `\`${it}\``; @@ -911,9 +901,8 @@ class PgAlterViewSchemaConvertor extends Convertor { convert(st: JsonAlterViewAlterSchemaStatement) { const { fromSchema, toSchema, name, materialized } = st; - const statement = `ALTER${ - materialized ? ' MATERIALIZED' : '' - } VIEW "${fromSchema}"."${name}" SET SCHEMA "${toSchema}";`; + const statement = `ALTER${materialized ? ' MATERIALIZED' : '' + } VIEW "${fromSchema}"."${name}" SET SCHEMA "${toSchema}";`; return statement; } @@ -1018,26 +1007,20 @@ class PgAlterTableAlterColumnSetGenerated extends Convertor { : `"${unsquashedIdentity?.name}"`; const identityStatement = unsquashedIdentity - ? ` GENERATED ${ - unsquashedIdentity.type === 'always' ? 'ALWAYS' : 'BY DEFAULT' - } AS IDENTITY (sequence name ${identityWithSchema}${ - unsquashedIdentity.increment - ? ` INCREMENT BY ${unsquashedIdentity.increment}` - : '' - }${ - unsquashedIdentity.minValue - ? ` MINVALUE ${unsquashedIdentity.minValue}` - : '' - }${ - unsquashedIdentity.maxValue - ? ` MAXVALUE ${unsquashedIdentity.maxValue}` - : '' - }${ - unsquashedIdentity.startWith - ? ` START WITH ${unsquashedIdentity.startWith}` - : '' - }${unsquashedIdentity.cache ? ` CACHE ${unsquashedIdentity.cache}` : ''}${ - unsquashedIdentity.cycle ? ` CYCLE` : '' + ? ` GENERATED ${unsquashedIdentity.type === 'always' ? 'ALWAYS' : 'BY DEFAULT' + } AS IDENTITY (sequence name ${identityWithSchema}${unsquashedIdentity.increment + ? ` INCREMENT BY ${unsquashedIdentity.increment}` + : '' + }${unsquashedIdentity.minValue + ? ` MINVALUE ${unsquashedIdentity.minValue}` + : '' + }${unsquashedIdentity.maxValue + ? ` MAXVALUE ${unsquashedIdentity.maxValue}` + : '' + }${unsquashedIdentity.startWith + ? ` START WITH ${unsquashedIdentity.startWith}` + : '' + }${unsquashedIdentity.cache ? ` CACHE ${unsquashedIdentity.cache}` : ''}${unsquashedIdentity.cycle ? ` CYCLE` : '' })` : ''; @@ -1088,8 +1071,7 @@ class PgAlterTableAlterColumnAlterGenerated extends Convertor { if (unsquashedOldIdentity.type !== unsquashedIdentity.type) { statementsToReturn.push( - `ALTER TABLE ${tableNameWithSchema} ALTER COLUMN "${columnName}" SET GENERATED ${ - unsquashedIdentity.type === 'always' ? 'ALWAYS' : 'BY DEFAULT' + `ALTER TABLE ${tableNameWithSchema} ALTER COLUMN "${columnName}" SET GENERATED ${unsquashedIdentity.type === 'always' ? 'ALWAYS' : 'BY DEFAULT' };`, ); } @@ -1126,8 +1108,7 @@ class PgAlterTableAlterColumnAlterGenerated extends Convertor { if (unsquashedOldIdentity.cycle !== unsquashedIdentity.cycle) { statementsToReturn.push( - `ALTER TABLE ${tableNameWithSchema} ALTER COLUMN "${columnName}" SET ${ - unsquashedIdentity.cycle ? `CYCLE` : 'NO CYCLE' + `ALTER TABLE ${tableNameWithSchema} ALTER COLUMN "${columnName}" SET ${unsquashedIdentity.cycle ? `CYCLE` : 'NO CYCLE' };`, ); } @@ -1149,9 +1130,8 @@ class PgAlterTableAddUniqueConstraintConvertor extends Convertor { ? `"${statement.schema}"."${statement.tableName}"` : `"${statement.tableName}"`; - return `ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${unsquashed.name}" UNIQUE${ - unsquashed.nullsNotDistinct ? ' NULLS NOT DISTINCT' : '' - }("${unsquashed.columns.join('","')}");`; + return `ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${unsquashed.name}" UNIQUE${unsquashed.nullsNotDistinct ? ' NULLS NOT DISTINCT' : '' + }("${unsquashed.columns.join('","')}");`; } } @@ -1211,9 +1191,8 @@ class MySQLAlterTableAddUniqueConstraintConvertor extends Convertor { convert(statement: JsonCreateUniqueConstraint): string { const unsquashed = MySqlSquasher.unsquashUnique(statement.data); - return `ALTER TABLE \`${statement.tableName}\` ADD CONSTRAINT \`${unsquashed.name}\` UNIQUE(\`${ - unsquashed.columns.join('`,`') - }\`);`; + return `ALTER TABLE \`${statement.tableName}\` ADD CONSTRAINT \`${unsquashed.name}\` UNIQUE(\`${unsquashed.columns.join('`,`') + }\`);`; } } @@ -1249,9 +1228,8 @@ class SingleStoreAlterTableAddUniqueConstraintConvertor extends Convertor { convert(statement: JsonCreateUniqueConstraint): string { const unsquashed = SingleStoreSquasher.unsquashUnique(statement.data); - return `ALTER TABLE \`${statement.tableName}\` ADD CONSTRAINT \`${unsquashed.name}\` UNIQUE(\`${ - unsquashed.columns.join('`,`') - }\`);`; + return `ALTER TABLE \`${statement.tableName}\` ADD CONSTRAINT \`${unsquashed.name}\` UNIQUE(\`${unsquashed.columns.join('`,`') + }\`);`; } } class SingleStoreAlterTableDropUniqueConstraintConvertor extends Convertor { @@ -1288,11 +1266,9 @@ class CreatePgSequenceConvertor extends Convertor { const sequenceWithSchema = schema ? `"${schema}"."${name}"` : `"${name}"`; - return `CREATE SEQUENCE ${sequenceWithSchema}${values.increment ? ` INCREMENT BY ${values.increment}` : ''}${ - values.minValue ? ` MINVALUE ${values.minValue}` : '' - }${values.maxValue ? ` MAXVALUE ${values.maxValue}` : ''}${ - values.startWith ? ` START WITH ${values.startWith}` : '' - }${values.cache ? ` CACHE ${values.cache}` : ''}${values.cycle ? ` CYCLE` : ''};`; + return `CREATE SEQUENCE ${sequenceWithSchema}${values.increment ? ` INCREMENT BY ${values.increment}` : ''}${values.minValue ? ` MINVALUE ${values.minValue}` : '' + }${values.maxValue ? ` MAXVALUE ${values.maxValue}` : ''}${values.startWith ? ` START WITH ${values.startWith}` : '' + }${values.cache ? ` CACHE ${values.cache}` : ''}${values.cycle ? ` CYCLE` : ''};`; } } @@ -1359,11 +1335,9 @@ class AlterPgSequenceConvertor extends Convertor { const sequenceWithSchema = schema ? `"${schema}"."${name}"` : `"${name}"`; - return `ALTER SEQUENCE ${sequenceWithSchema}${increment ? ` INCREMENT BY ${increment}` : ''}${ - minValue ? ` MINVALUE ${minValue}` : '' - }${maxValue ? ` MAXVALUE ${maxValue}` : ''}${startWith ? ` START WITH ${startWith}` : ''}${ - cache ? ` CACHE ${cache}` : '' - }${cycle ? ` CYCLE` : ''};`; + return `ALTER SEQUENCE ${sequenceWithSchema}${increment ? ` INCREMENT BY ${increment}` : ''}${minValue ? ` MINVALUE ${minValue}` : '' + }${maxValue ? ` MAXVALUE ${maxValue}` : ''}${startWith ? ` START WITH ${startWith}` : ''}${cache ? ` CACHE ${cache}` : '' + }${cycle ? ` CYCLE` : ''};`; } } @@ -1745,26 +1719,20 @@ class PgAlterTableAddColumnConvertor extends Convertor { : `"${unsquashedIdentity?.name}"`; const identityStatement = unsquashedIdentity - ? ` GENERATED ${ - unsquashedIdentity.type === 'always' ? 'ALWAYS' : 'BY DEFAULT' - } AS IDENTITY (sequence name ${identityWithSchema}${ - unsquashedIdentity.increment - ? ` INCREMENT BY ${unsquashedIdentity.increment}` - : '' - }${ - unsquashedIdentity.minValue - ? ` MINVALUE ${unsquashedIdentity.minValue}` - : '' - }${ - unsquashedIdentity.maxValue - ? ` MAXVALUE ${unsquashedIdentity.maxValue}` - : '' - }${ - unsquashedIdentity.startWith - ? ` START WITH ${unsquashedIdentity.startWith}` - : '' - }${unsquashedIdentity.cache ? ` CACHE ${unsquashedIdentity.cache}` : ''}${ - unsquashedIdentity.cycle ? ` CYCLE` : '' + ? ` GENERATED ${unsquashedIdentity.type === 'always' ? 'ALWAYS' : 'BY DEFAULT' + } AS IDENTITY (sequence name ${identityWithSchema}${unsquashedIdentity.increment + ? ` INCREMENT BY ${unsquashedIdentity.increment}` + : '' + }${unsquashedIdentity.minValue + ? ` MINVALUE ${unsquashedIdentity.minValue}` + : '' + }${unsquashedIdentity.maxValue + ? ` MAXVALUE ${unsquashedIdentity.maxValue}` + : '' + }${unsquashedIdentity.startWith + ? ` START WITH ${unsquashedIdentity.startWith}` + : '' + }${unsquashedIdentity.cache ? ` CACHE ${unsquashedIdentity.cache}` : ''}${unsquashedIdentity.cycle ? ` CYCLE` : '' })` : ''; @@ -1853,11 +1821,10 @@ export class SQLiteAlterTableAddColumnConvertor extends Convertor { const referenceAsObject = referenceData ? SQLiteSquasher.unsquashFK(referenceData) : undefined; - const referenceStatement = `${ - referenceAsObject - ? ` REFERENCES ${referenceAsObject.tableTo}(${referenceAsObject.columnsTo})` - : '' - }`; + const referenceStatement = `${referenceAsObject + ? ` REFERENCES ${referenceAsObject.tableTo}(${referenceAsObject.columnsTo})` + : '' + }`; // const autoincrementStatement = `${autoincrement ? 'AUTO_INCREMENT' : ''}` const generatedStatement = generated ? ` GENERATED ALWAYS AS ${generated.as} ${generated.type.toUpperCase()}` @@ -3005,9 +2972,8 @@ class PgAlterTableCreateCompositePrimaryKeyConvertor extends Convertor { ? `"${statement.schema}"."${statement.tableName}"` : `"${statement.tableName}"`; - return `ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${statement.constraintName}" PRIMARY KEY("${ - columns.join('","') - }");`; + return `ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${statement.constraintName}" PRIMARY KEY("${columns.join('","') + }");`; } } class PgAlterTableDeleteCompositePrimaryKeyConvertor extends Convertor { @@ -3041,9 +3007,8 @@ class PgAlterTableAlterCompositePrimaryKeyConvertor extends Convertor { ? `"${statement.schema}"."${statement.tableName}"` : `"${statement.tableName}"`; - return `ALTER TABLE ${tableNameWithSchema} DROP CONSTRAINT "${statement.oldConstraintName}";\n${BREAKPOINT}ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${statement.newConstraintName}" PRIMARY KEY("${ - newColumns.join('","') - }");`; + return `ALTER TABLE ${tableNameWithSchema} DROP CONSTRAINT "${statement.oldConstraintName}";\n${BREAKPOINT}ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${statement.newConstraintName}" PRIMARY KEY("${newColumns.join('","') + }");`; } } @@ -3438,12 +3403,10 @@ class CreatePgIndexConvertor extends Convertor { const value = columns .map( (it) => - `${it.isExpression ? it.expression : `"${it.expression}"`}${ - it.opclass ? ` ${it.opclass}` : it.asc ? '' : ' DESC' - }${ - (it.asc && it.nulls && it.nulls === 'last') || it.opclass - ? '' - : ` NULLS ${it.nulls!.toUpperCase()}` + `${it.isExpression ? it.expression : `"${it.expression}"`}${it.opclass ? ` ${it.opclass}` : it.asc ? '' : ' DESC' + }${(it.asc && it.nulls && it.nulls === 'last') || it.opclass + ? '' + : ` NULLS ${it.nulls!.toUpperCase()}` }`, ) .join(','); @@ -3463,13 +3426,11 @@ class CreatePgIndexConvertor extends Convertor { return reversedString; } - return `CREATE ${indexPart}${ - concurrently ? ' CONCURRENTLY' : '' - } "${name}" ON ${tableNameWithSchema} USING ${method} (${value})${ - Object.keys(withMap!).length !== 0 + return `CREATE ${indexPart}${concurrently ? ' CONCURRENTLY' : '' + } "${name}" ON ${tableNameWithSchema} USING ${method} (${value})${Object.keys(withMap!).length !== 0 ? ` WITH (${reverseLogic(withMap!)})` : '' - }${where ? ` WHERE ${where}` : ''};`; + }${where ? ` WHERE ${where}` : ''};`; } } @@ -3480,13 +3441,13 @@ class CreateMySqlIndexConvertor extends Convertor { convert(statement: JsonCreateIndexStatement): string { // should be changed - const { name, columns, isUnique } = MySqlSquasher.unsquashIdx( + const { name, columns, isUnique, raw } = MySqlSquasher.unsquashIdx( statement.data, ); const indexPart = isUnique ? 'UNIQUE INDEX' : 'INDEX'; const uniqueString = columns - .map((it) => { + ?.map((it) => { return statement.internal?.indexes ? statement.internal?.indexes[name]?.columns[it]?.isExpression ? it @@ -3495,7 +3456,7 @@ class CreateMySqlIndexConvertor extends Convertor { }) .join(','); - return `CREATE ${indexPart} \`${name}\` ON \`${statement.tableName}\` (${uniqueString});`; + return raw ? decodeRaw(raw) as string : `CREATE ${indexPart} \`${name}\` ON \`${statement.tableName}\` (${uniqueString});`; } } diff --git a/drizzle-kit/tests/mysql-generated.test.ts b/drizzle-kit/tests/mysql-generated.test.ts index 3531582d0..af01e3dc0 100644 --- a/drizzle-kit/tests/mysql-generated.test.ts +++ b/drizzle-kit/tests/mysql-generated.test.ts @@ -1,5 +1,5 @@ import { SQL, sql } from 'drizzle-orm'; -import { int, mysqlTable, text } from 'drizzle-orm/mysql-core'; +import { bigint, customType, customIndex, int, mysqlTable, text, varchar } from 'drizzle-orm/mysql-core'; import { expect, test } from 'vitest'; import { diffTestSchemasMysql } from './schemaDiffer'; @@ -1288,3 +1288,102 @@ test('generated as string: change generated constraint', async () => { "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') VIRTUAL;", ]); }); + +export const vector = customType<{ + data: string; + config: { length: number }; + configRequired: true; +}>({ + dataType(config) { + return `VECTOR(${config.length})`; + }, +}); + +test.only('generated as string: custom index using sql', async () => { + const from = {}; + const to = { + users: mysqlTable('users', { + id: bigint({ mode: "bigint" }).autoincrement().primaryKey(), + name: varchar({ length: 255 }).notNull(), + embedding: vector("embedding", { length: 3 }), + }, () => { + return { + idx_embedding_oceanbase: customIndex({ + name: 'idx_embedding_oceanbase', + raw: 'CREATE VECTOR INDEX idx_embedding_oceanbase ON users(embedding) WITH (distance=L2, type=hnsw);', + }), + idx_embedding_oceanbase_without_semicolon: customIndex({ + name: 'idx_embedding_oceanbase_without_semicolon', + raw: 'CREATE VECTOR INDEX idx_embedding_oceanbase_without_semicolon ON users(embedding) WITH (distance=L2, type=hnsw)', + }), + idx_embedding_mysql: customIndex({ + name: 'idx_embedding_mysql', + raw: 'CREATE VECTOR INDEX idx_embedding_mysql ON users(embedding) SECONDARY_ENGINE_ATTRIBUTE=\'{"type":"spann", "distance":"cosine"}\';', + }), + }; + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasMysql( + from, + to, + [], + ); + console.log(sqlStatements) + + // expect(statements).toStrictEqual([ + // { + // type: "create_table", + // tableName: "users", + // schema: undefined, + // columns: [ + // { + // name: "id", + // type: "bigint", + // primaryKey: false, + // notNull: true, + // autoincrement: true, + // }, + // { + // name: "name", + // type: "varchar(255)", + // primaryKey: false, + // notNull: true, + // autoincrement: false, + // }, + // { + // name: "embedding", + // type: "VECTOR(3)", + // primaryKey: false, + // notNull: false, + // autoincrement: false, + // }, + // ], + // compositePKs: ["users_id;id"], + // compositePkName: "users_id", + // uniqueConstraints: [], + // internals: { tables: {}, indexes: {} }, + // checkConstraints: [], + // }, + // { + // type: "create_index", + // tableName: "users", + // data: 'idx_embedding;embedding;false;true;;;;{"type":"spann", "distance":"cosine"}', + // schema: undefined, + // internal: { tables: {}, indexes: {} }, + // }, + // ]); + + expect(sqlStatements).toStrictEqual([ + `CREATE TABLE \`users\` ( + \`id\` bigint AUTO_INCREMENT NOT NULL, + \`name\` varchar(255) NOT NULL, + \`embedding\` VECTOR(3), + CONSTRAINT \`users_id\` PRIMARY KEY(\`id\`) +); +`, + 'CREATE VECTOR INDEX idx_embedding_oceanbase ON users(embedding) WITH (distance=L2, type=hnsw);', + 'CREATE VECTOR INDEX idx_embedding_oceanbase_without_semicolon ON users(embedding) WITH (distance=L2, type=hnsw);', + 'CREATE VECTOR INDEX idx_embedding_mysql ON users(embedding) SECONDARY_ENGINE_ATTRIBUTE=\'{"type":"spann", "distance":"cosine"}\';', + ]); +}); diff --git a/drizzle-orm/src/mysql-core/indexes.ts b/drizzle-orm/src/mysql-core/indexes.ts index 5b73b1d30..0b16386c2 100644 --- a/drizzle-orm/src/mysql-core/indexes.ts +++ b/drizzle-orm/src/mysql-core/indexes.ts @@ -27,6 +27,11 @@ interface IndexConfig { * If set, adds locks to the index creation. */ lock?: 'default' | 'none' | 'shared' | 'exclusive'; + + /** + * If set, use raw sql to create index. + */ + raw?: string; } export type IndexColumn = MySqlColumn | SQL; @@ -34,7 +39,7 @@ export type IndexColumn = MySqlColumn | SQL; export class IndexBuilderOn { static readonly [entityKind]: string = 'MySqlIndexBuilderOn'; - constructor(private name: string, private unique: boolean) {} + constructor(private name: string, private unique: boolean) { } on(...columns: [IndexColumn, ...IndexColumn[]]): IndexBuilder { return new IndexBuilder(this.name, columns, this.unique); @@ -46,7 +51,7 @@ export interface AnyIndexBuilder { } // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface IndexBuilder extends AnyIndexBuilder {} +export interface IndexBuilder extends AnyIndexBuilder { } export class IndexBuilder implements AnyIndexBuilder { static readonly [entityKind]: string = 'MySqlIndexBuilder'; @@ -54,10 +59,13 @@ export class IndexBuilder implements AnyIndexBuilder { /** @internal */ config: IndexConfig; - constructor(name: string, columns: IndexColumn[], unique: boolean) { - this.config = { + constructor(name: string, columns: IndexColumn[], unique: boolean); + constructor(config: IndexConfig); + + constructor(name: string | IndexConfig, columns?: IndexColumn[], unique?: boolean) { + typeof name === 'object' ? this.config = name : this.config = { name, - columns, + columns: columns as IndexColumn[], unique, }; } @@ -99,6 +107,10 @@ export type GetColumnsTableName = TColumns extends >[] ? TTableName : never; +export interface OptionalIndexConfig extends Omit { + columns?: IndexColumn[]; +} + export function index(name: string): IndexBuilderOn { return new IndexBuilderOn(name, false); } @@ -106,3 +118,7 @@ export function index(name: string): IndexBuilderOn { export function uniqueIndex(name: string): IndexBuilderOn { return new IndexBuilderOn(name, true); } + +export function customIndex(config: OptionalIndexConfig): IndexBuilder { + return new IndexBuilder({ unique: false, columns: [], ...config, raw: config.raw?.endsWith(';') ? config.raw : `${config.raw};` }); +} diff --git a/drizzle-orm/src/mysql-core/utils.ts b/drizzle-orm/src/mysql-core/utils.ts index b49dd0043..952fbec32 100644 --- a/drizzle-orm/src/mysql-core/utils.ts +++ b/drizzle-orm/src/mysql-core/utils.ts @@ -31,6 +31,7 @@ export function getTableConfig(table: MySqlTable) { if (extraConfigBuilder !== undefined) { const extraConfig = extraConfigBuilder(table[MySqlTable.Symbol.Columns]); const extraValues = Array.isArray(extraConfig) ? extraConfig.flat(1) as any[] : Object.values(extraConfig); + console.log(extraValues) for (const builder of Object.values(extraValues)) { if (is(builder, IndexBuilder)) { indexes.push(builder.build(table));