From fb93b31846796c7e3b886493aeef08afc09bf5bc Mon Sep 17 00:00:00 2001 From: Gabriel Cipriano Date: Thu, 13 Mar 2025 14:01:08 +0100 Subject: [PATCH 1/2] fix: rm unique constraint as its not supported by spanner --- drizzle-orm/src/googlesql/columns/common.ts | 13 +--- drizzle-orm/src/googlesql/index.ts | 1 - drizzle-orm/src/googlesql/table.ts | 4 +- .../src/googlesql/unique-constraint.ts | 65 ------------------- drizzle-orm/src/googlesql/utils.ts | 5 -- .../tests/casing/googlesql-to-snake.test.ts | 6 +- 6 files changed, 6 insertions(+), 88 deletions(-) delete mode 100644 drizzle-orm/src/googlesql/unique-constraint.ts diff --git a/drizzle-orm/src/googlesql/columns/common.ts b/drizzle-orm/src/googlesql/columns/common.ts index 25113cfc02..98259da5c4 100644 --- a/drizzle-orm/src/googlesql/columns/common.ts +++ b/drizzle-orm/src/googlesql/columns/common.ts @@ -18,7 +18,6 @@ import { ForeignKeyBuilder } from '~/googlesql/foreign-keys.ts'; import type { AnyGoogleSqlTable, GoogleSqlTable } from '~/googlesql/table.ts'; import type { SQL } from '~/sql/sql.ts'; import type { Update } from '~/utils.ts'; -import { uniqueKeyName } from '../unique-constraint.ts'; export interface ReferenceConfig { ref: () => GoogleSqlColumn; @@ -56,18 +55,12 @@ export abstract class GoogleSqlColumnBuilder< return this; } - unique(name?: string): this { - this.config.isUnique = true; - this.config.uniqueName = name; - return this; - } - + // This "always" keyword doesn't exist in google sql, we only have it for compatibility with other dialects. generatedAlwaysAs(as: SQL | T['data'] | (() => SQL), config?: GoogleSqlGeneratedColumnConfig): HasGenerated { this.config.generated = { as, - type: 'always', mode: config?.mode ?? 'virtual', }; return this as any; @@ -110,9 +103,6 @@ export abstract class GoogleSqlColumn< override readonly table: GoogleSqlTable, config: ColumnBuilderRuntimeConfig, ) { - if (!config.uniqueName) { - config.uniqueName = uniqueKeyName(table, [config.name]); - } super(table, config); } } @@ -126,7 +116,6 @@ export type AnyGoogleSqlColumn = ColumnBuilderBaseConfig, diff --git a/drizzle-orm/src/googlesql/index.ts b/drizzle-orm/src/googlesql/index.ts index 204e0af3c4..7ea682e733 100644 --- a/drizzle-orm/src/googlesql/index.ts +++ b/drizzle-orm/src/googlesql/index.ts @@ -11,7 +11,6 @@ export * from './schema.ts'; export * from './session.ts'; export * from './subquery.ts'; export * from './table.ts'; -export * from './unique-constraint.ts'; export * from './utils.ts'; export * from './view-common.ts'; export * from './view.ts'; diff --git a/drizzle-orm/src/googlesql/table.ts b/drizzle-orm/src/googlesql/table.ts index 6e71d2be3e..4a9956ff9e 100644 --- a/drizzle-orm/src/googlesql/table.ts +++ b/drizzle-orm/src/googlesql/table.ts @@ -7,14 +7,12 @@ import type { GoogleSqlColumn, GoogleSqlColumnBuilder, GoogleSqlColumnBuilderBas import type { ForeignKey, ForeignKeyBuilder } from './foreign-keys.ts'; import type { AnyIndexBuilder } from './indexes.ts'; import type { PrimaryKeyBuilder } from './primary-keys.ts'; -import type { UniqueConstraintBuilder } from './unique-constraint.ts'; export type GoogleSqlTableExtraConfigValue = | AnyIndexBuilder | CheckBuilder | ForeignKeyBuilder - | PrimaryKeyBuilder - | UniqueConstraintBuilder; + | PrimaryKeyBuilder; export type GoogleSqlTableExtraConfig = Record< string, diff --git a/drizzle-orm/src/googlesql/unique-constraint.ts b/drizzle-orm/src/googlesql/unique-constraint.ts deleted file mode 100644 index 1bc72b8ce5..0000000000 --- a/drizzle-orm/src/googlesql/unique-constraint.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { entityKind } from '~/entity.ts'; -import { TableName } from '~/table.utils.ts'; -import type { GoogleSqlColumn } from './columns/index.ts'; -import type { GoogleSqlTable } from './table.ts'; - -export function unique(name?: string): UniqueOnConstraintBuilder { - return new UniqueOnConstraintBuilder(name); -} - -export function uniqueKeyName(table: GoogleSqlTable, columns: string[]) { - return `${table[TableName]}_${columns.join('_')}_unique`; -} - -export class UniqueConstraintBuilder { - static readonly [entityKind]: string = 'GoogleSqlUniqueConstraintBuilder'; - - /** @internal */ - columns: GoogleSqlColumn[]; - - constructor( - columns: GoogleSqlColumn[], - private name?: string, - ) { - this.columns = columns; - } - - /** @internal */ - build(table: GoogleSqlTable): UniqueConstraint { - return new UniqueConstraint(table, this.columns, this.name); - } -} - -export class UniqueOnConstraintBuilder { - static readonly [entityKind]: string = 'GoogleSqlUniqueOnConstraintBuilder'; - - /** @internal */ - name?: string; - - constructor( - name?: string, - ) { - this.name = name; - } - - on(...columns: [GoogleSqlColumn, ...GoogleSqlColumn[]]) { - return new UniqueConstraintBuilder(columns, this.name); - } -} - -export class UniqueConstraint { - static readonly [entityKind]: string = 'GoogleSqlUniqueConstraint'; - - readonly columns: GoogleSqlColumn[]; - readonly name?: string; - readonly nullsNotDistinct: boolean = false; - - constructor(readonly table: GoogleSqlTable, columns: GoogleSqlColumn[], name?: string) { - this.columns = columns; - this.name = name ?? uniqueKeyName(this.table, this.columns.map((column) => column.name)); - } - - getName() { - return this.name; - } -} diff --git a/drizzle-orm/src/googlesql/utils.ts b/drizzle-orm/src/googlesql/utils.ts index 90ed0c45a1..cb6f2145cf 100644 --- a/drizzle-orm/src/googlesql/utils.ts +++ b/drizzle-orm/src/googlesql/utils.ts @@ -11,7 +11,6 @@ import type { PrimaryKey } from './primary-keys.ts'; import { PrimaryKeyBuilder } from './primary-keys.ts'; import type { IndexForHint } from './query-builders/select.ts'; import { GoogleSqlTable } from './table.ts'; -import { type UniqueConstraint, UniqueConstraintBuilder } from './unique-constraint.ts'; import { GoogleSqlViewConfig } from './view-common.ts'; import type { GoogleSqlView } from './view.ts'; @@ -20,7 +19,6 @@ export function getTableConfig(table: GoogleSqlTable) { const indexes: Index[] = []; const checks: Check[] = []; const primaryKeys: PrimaryKey[] = []; - const uniqueConstraints: UniqueConstraint[] = []; const foreignKeys: ForeignKey[] = Object.values(table[GoogleSqlTable.Symbol.InlineForeignKeys]); const name = table[Table.Symbol.Name]; const schema = table[Table.Symbol.Schema]; @@ -36,8 +34,6 @@ export function getTableConfig(table: GoogleSqlTable) { indexes.push(builder.build(table)); } else if (is(builder, CheckBuilder)) { checks.push(builder.build(table)); - } else if (is(builder, UniqueConstraintBuilder)) { - uniqueConstraints.push(builder.build(table)); } else if (is(builder, PrimaryKeyBuilder)) { primaryKeys.push(builder.build(table)); } else if (is(builder, ForeignKeyBuilder)) { @@ -52,7 +48,6 @@ export function getTableConfig(table: GoogleSqlTable) { foreignKeys, checks, primaryKeys, - uniqueConstraints, name, schema, baseName, diff --git a/drizzle-orm/tests/casing/googlesql-to-snake.test.ts b/drizzle-orm/tests/casing/googlesql-to-snake.test.ts index 35d7558877..f5df2fcabd 100644 --- a/drizzle-orm/tests/casing/googlesql-to-snake.test.ts +++ b/drizzle-orm/tests/casing/googlesql-to-snake.test.ts @@ -5,6 +5,8 @@ import { relations } from '~/relations'; import { drizzle as spanner } from '~/spanner'; import { asc, eq, sql } from '~/sql'; +// TODO: SPANNER: rewrite tests + const testSchema = googlesqlSchema('test'); const users = googlesqlTable('users', { id: serial().primaryKey(), @@ -200,13 +202,13 @@ describe('googlesql to snake case', () => { params: ['John', 'Doe', 30], }); expect(db.dialect.casing.cache).toEqual(usersCache); - }); + }); it('insert (on duplicate key update)', ({ expect }) => { const query = db .insert(users) .values({ firstName: 'John', lastName: 'Doe', age: 30 }) - .onDuplicateKeyUpdate({ set: { age: 31 } }); + .onDuplicateKeyUpdate(); expect(query.toSQL()).toEqual({ sql: From f6aec64ac397b93a4aa849102f3ee4bd8e172703 Mon Sep 17 00:00:00 2001 From: Gabriel Cipriano Date: Thu, 13 Mar 2025 14:21:57 +0100 Subject: [PATCH 2/2] feat: googlesql serializer --- drizzle-kit/src/jsonStatements.ts | 321 +++------ drizzle-kit/src/schemaValidator.ts | 2 +- drizzle-kit/src/serializer/googlesqlSchema.ts | 166 +---- .../src/serializer/googlesqlSerializer.ts | 660 ++---------------- drizzle-kit/src/snapshotsDiffer.ts | 167 ++--- drizzle-kit/src/utils.ts | 4 + 6 files changed, 240 insertions(+), 1080 deletions(-) diff --git a/drizzle-kit/src/jsonStatements.ts b/drizzle-kit/src/jsonStatements.ts index b53663a7e1..53e40ca634 100644 --- a/drizzle-kit/src/jsonStatements.ts +++ b/drizzle-kit/src/jsonStatements.ts @@ -2,6 +2,12 @@ import chalk from 'chalk'; import { getNewTableName } from './cli/commands/sqlitePushUtils'; import { warning } from './cli/views'; import { CommonSquashedSchema } from './schemaValidator'; +import { + GoogleSqlKitInternals, + GoogleSqlSchema, + GoogleSqlSquasher, + View as GoogleSqlView, +} from './serializer/googlesqlSchema'; import { MySqlKitInternals, MySqlSchema, MySqlSquasher, View as MySqlView } from './serializer/mysqlSchema'; import { Index, @@ -22,7 +28,6 @@ import { View as SqliteView, } from './serializer/sqliteSchema'; import { AlteredColumn, Column, Sequence, Table } from './snapshotsDiffer'; -import { GoogleSqlKitInternals, GoogleSqlSchema, GoogleSqlSquasher, View as GoogleSqlView } from './serializer/googlesqlSchema'; export interface JsonSqliteCreateTableStatement { type: 'sqlite_create_table'; @@ -683,7 +688,6 @@ export type JsonCreateMySqlViewStatement = { replace: boolean; } & Omit; - export type JsonCreateGoogleSqlViewStatement = { type: 'googlesql_create_view'; replace: boolean; @@ -950,7 +954,7 @@ export const prepareGoogleSqlCreateTableJson = ( // if previously it was an expression or column internals: GoogleSqlKitInternals, ): JsonCreateTableStatement => { - const { name, schema, columns, compositePrimaryKeys, uniqueConstraints, checkConstraints } = table; + const { name, schema, columns, compositePrimaryKeys, checkConstraints } = table; return { type: 'create_table', @@ -964,13 +968,11 @@ export const prepareGoogleSqlCreateTableJson = ( .name ].name : '', - uniqueConstraints: Object.values(uniqueConstraints), internals, checkConstraints: Object.values(checkConstraints), }; }; - export const prepareSingleStoreCreateTableJson = ( table: Table, // TODO: remove? @@ -1730,7 +1732,6 @@ export const prepareAlterColumnsMysql = ( return [...dropPkStatements, ...setPkStatements, ...statements]; }; - // TODO - SPANNER - verify export const prepareAlterColumnsGooglesql = ( tableName: string, @@ -1742,73 +1743,78 @@ export const prepareAlterColumnsGooglesql = ( action?: 'push' | undefined, ): JsonAlterColumnStatement[] => { let statements: JsonAlterColumnStatement[] = []; - let dropPkStatements: JsonAlterColumnDropPrimaryKeyStatement[] = []; - let setPkStatements: JsonAlterColumnSetPrimaryKeyStatement[] = []; + // TODO - SPANNER - support autoincrement for (const column of columns) { - const columnName = typeof column.name !== 'string' ? column.name.new : column.name; - - const table = json2.tables[tableName]; - const snapshotColumn = table.columns[columnName]; - - const columnType = snapshotColumn.type; - const columnDefault = snapshotColumn.default; - const columnOnUpdate = 'onUpdate' in snapshotColumn ? snapshotColumn.onUpdate : undefined; - const columnNotNull = table.columns[columnName].notNull; - - const columnAutoIncrement = 'autoincrement' in snapshotColumn - ? snapshotColumn.autoincrement ?? false - : false; - - const columnPk = table.columns[columnName].primaryKey; - - if (column.autoincrement?.type === 'added') { - statements.push({ - type: 'alter_table_alter_column_set_autoincrement', - tableName, - columnName, - schema, - newDataType: columnType, - columnDefault, - columnOnUpdate, - columnNotNull, - columnAutoIncrement, - columnPk, - }); - } - - if (column.autoincrement?.type === 'changed') { - const type = column.autoincrement.new - ? 'alter_table_alter_column_set_autoincrement' - : 'alter_table_alter_column_drop_autoincrement'; - - statements.push({ - type, - tableName, - columnName, - schema, - newDataType: columnType, - columnDefault, - columnOnUpdate, - columnNotNull, - columnAutoIncrement, - columnPk, - }); - } + if (column.autoincrement?.type) { + warning( + `Autoincrement is not supported yet. The autoincrement field in column ${column.name} will be ignored.`, + ); - if (column.autoincrement?.type === 'deleted') { - statements.push({ - type: 'alter_table_alter_column_drop_autoincrement', - tableName, - columnName, - schema, - newDataType: columnType, - columnDefault, - columnOnUpdate, - columnNotNull, - columnAutoIncrement, - columnPk, - }); + // const columnName = typeof column.name !== 'string' ? column.name.new : column.name; + + // const table = json2.tables[tableName]; + // const snapshotColumn = table.columns[columnName]; + + // const columnType = snapshotColumn.type; + // const columnDefault = snapshotColumn.default; + // const columnOnUpdate = 'onUpdate' in snapshotColumn ? snapshotColumn.onUpdate : undefined; + // const columnNotNull = table.columns[columnName].notNull; + + // const columnAutoIncrement = 'autoincrement' in snapshotColumn + // ? snapshotColumn.autoincrement ?? false + // : false; + + // const columnPk = table.columns[columnName].primaryKey; + + // if (column.autoincrement?.type === 'added') { + // statements.push({ + // type: 'alter_table_alter_column_set_autoincrement', + // tableName, + // columnName, + // schema, + // newDataType: columnType, + // columnDefault, + // columnOnUpdate, + // columnNotNull, + // columnAutoIncrement, + // columnPk, + // }); + // } + + // if (column.autoincrement?.type === 'changed') { + // const type = column.autoincrement.new + // ? 'alter_table_alter_column_set_autoincrement' + // : 'alter_table_alter_column_drop_autoincrement'; + + // statements.push({ + // type, + // tableName, + // columnName, + // schema, + // newDataType: columnType, + // columnDefault, + // columnOnUpdate, + // columnNotNull, + // columnAutoIncrement, + // columnPk, + // }); + // } + + // if (column.autoincrement?.type === 'deleted') { + // statements.push({ + // type: 'alter_table_alter_column_drop_autoincrement', + // tableName, + // columnName, + // schema, + // newDataType: columnType, + // columnDefault, + // columnOnUpdate, + // columnNotNull, + // columnAutoIncrement, + // columnPk, + // }); + // } } } @@ -1823,9 +1829,11 @@ export const prepareAlterColumnsGooglesql = ( const columnOnUpdate = (json2.tables[tableName].columns[columnName] as any) .onUpdate; const columnNotNull = json2.tables[tableName].columns[columnName].notNull; - const columnAutoIncrement = ( - json2.tables[tableName].columns[columnName] as any - ).autoincrement; + // TODO - SPANNER - support autoincrement + const columnAutoIncrement = false; + // const columnAutoIncrement = ( + // json2.tables[tableName].columns[columnName] as any + // ).autoincrement; const columnPk = (json2.tables[tableName].columns[columnName] as any) .primaryKey; @@ -1834,16 +1842,19 @@ export const prepareAlterColumnsGooglesql = ( ]; if (typeof column.name !== 'string') { - statements.push({ - type: 'alter_table_rename_column', - tableName, - oldColumnName: column.name.old, - newColumnName: column.name.new, - schema, - }); + throw new Error('Renaming columns is not supported in Google Cloud Spanner'); } if (column.type?.type === 'changed') { + function isStringOrBytes(type: string): boolean { + const lowerType = type.toLowerCase(); + return lowerType.startsWith('string') || lowerType.startsWith('bytes'); + } + + // https://cloud.google.com/spanner/docs/schema-updates#supported-updates + if (!isStringOrBytes(column.type.new) || !isStringOrBytes(column.type.old)) { + throw new Error('Changing column types is only supported for STRING and BYTES types in Google Cloud Spanner'); + } statements.push({ type: 'alter_table_alter_column_set_type', tableName, @@ -1866,13 +1877,7 @@ export const prepareAlterColumnsGooglesql = ( && !column.primaryKey.new && typeof compositePk === 'undefined') ) { - dropPkStatements.push({ - //// - type: 'alter_table_alter_column_drop_pk', - tableName, - columnName, - schema, - }); + throw new Error('Dropping primary keys is not supported in Google Cloud Spanner'); } if (column.default?.type === 'added') { @@ -1921,6 +1926,10 @@ export const prepareAlterColumnsGooglesql = ( }); } + if (column.notNull?.type && columnPk) { + throw new Error('Adding NOT NULL constraint to a primary key column is not supported in Google Cloud Spanner'); + } + if (column.notNull?.type === 'added') { statements.push({ type: 'alter_table_alter_column_set_notnull', @@ -1970,31 +1979,13 @@ export const prepareAlterColumnsGooglesql = ( } if (column.generated?.type === 'added') { - if (columnGenerated?.type === 'virtual') { - warning( - `You are trying to add virtual generated constraint to ${ - chalk.blue( - columnName, - ) - } column. As MySQL docs mention: "Nongenerated columns can be altered to stored but not virtual generated columns". We will drop an existing column and add it with a virtual generated statement. This means that the data previously stored in this column will be wiped, and new data will be generated on each read for this column\n`, - ); - } - statements.push({ - type: 'alter_table_alter_column_set_generated', - tableName, - columnName, - schema, - newDataType: columnType, - columnDefault, - columnOnUpdate, - columnNotNull, - columnAutoIncrement, - columnPk, - columnGenerated, - }); + throw new Error('Google Cloud Spanner does not support transform an existing column to a generated column'); } if (column.generated?.type === 'changed' && action !== 'push') { + if (column.generated.new.type === 'stored' || column.generated.old.type === 'stored') { + throw new Error("Google Cloud Spanner doesn't support changing stored generated columns"); + } statements.push({ type: 'alter_table_alter_column_alter_generated', tableName, @@ -2011,49 +2002,17 @@ export const prepareAlterColumnsGooglesql = ( } if (column.generated?.type === 'deleted') { - if (columnGenerated?.type === 'virtual') { - warning( - `You are trying to remove virtual generated constraint from ${ - chalk.blue( - columnName, - ) - } column. As MySQL docs mention: "Stored but not virtual generated columns can be altered to nongenerated columns. The stored generated values become the values of the nongenerated column". We will drop an existing column and add it without a virtual generated statement. This means that this column will have no data after migration\n`, - ); - } - statements.push({ - type: 'alter_table_alter_column_drop_generated', - tableName, - columnName, - schema, - newDataType: columnType, - columnDefault, - columnOnUpdate, - columnNotNull, - columnAutoIncrement, - columnPk, - columnGenerated, - oldColumn: json1.tables[tableName].columns[columnName], - }); + throw new Error('Google Cloud Spanner does not support transform an generated column to a non-generated column'); } if ( column.primaryKey?.type === 'added' || (column.primaryKey?.type === 'changed' && column.primaryKey.new) ) { - const wasAutoincrement = statements.filter( - (it) => it.type === 'alter_table_alter_column_set_autoincrement', - ); - if (wasAutoincrement.length === 0) { - setPkStatements.push({ - type: 'alter_table_alter_column_set_pk', - tableName, - schema, - columnName, - }); - } + throw new Error('Adding/changing primary keys is not supported in Google Cloud Spanner'); } - if (column.onUpdate?.type === 'added') { + if (column.onUpdate?.type === 'added' || column.onUpdate?.type === 'changed') { statements.push({ type: 'alter_table_alter_column_set_on_update', tableName, @@ -2084,7 +2043,7 @@ export const prepareAlterColumnsGooglesql = ( } } - return [...dropPkStatements, ...setPkStatements, ...statements]; + return [...statements]; }; export const prepareAlterColumnsSingleStore = ( @@ -3718,74 +3677,6 @@ export const prepareAlterCompositePrimaryKeyMySql = ( }); }; -// TODO - SPANNER - verify -export const prepareAddCompositePrimaryKeyGoogleSql = ( - tableName: string, - pks: Record, - // TODO: remove? - json1: GoogleSqlSchema, - json2: GoogleSqlSchema, -): JsonCreateCompositePK[] => { - const res: JsonCreateCompositePK[] = []; - for (const it of Object.values(pks)) { - const unsquashed = GoogleSqlSquasher.unsquashPK(it); - - if ( - unsquashed.columns.length === 1 - && json1.tables[tableName]?.columns[unsquashed.columns[0]]?.primaryKey - ) { - continue; - } - - res.push({ - type: 'create_composite_pk', - tableName, - data: it, - constraintName: unsquashed.name, - } as JsonCreateCompositePK); - } - return res; -}; - -export const prepareDeleteCompositePrimaryKeyGoogleSql = ( - tableName: string, - pks: Record, - // TODO: remove? - json1: GoogleSqlSchema, -): JsonDeleteCompositePK[] => { - return Object.values(pks).map((it) => { - const unsquashed = GoogleSqlSquasher.unsquashPK(it); - return { - type: 'delete_composite_pk', - tableName, - data: it, - } as JsonDeleteCompositePK; - }); -}; - -export const prepareAlterCompositePrimaryKeyGoogleSql = ( - tableName: string, - pks: Record, - // TODO: remove? - json1: GoogleSqlSchema, - json2: GoogleSqlSchema, -): JsonAlterCompositePK[] => { - return Object.values(pks).map((it) => { - return { - type: 'alter_composite_pk', - tableName, - old: it.__old, - new: it.__new, - oldConstraintName: json1.tables[tableName].compositePrimaryKeys[ - MySqlSquasher.unsquashPK(it.__old).name - ].name, - newConstraintName: json2.tables[tableName].compositePrimaryKeys[ - MySqlSquasher.unsquashPK(it.__new).name - ].name, - } as JsonAlterCompositePK; - }); -}; - export const preparePgCreateViewJson = ( name: string, schema: string, @@ -3834,14 +3725,12 @@ export const prepareGoogleSqlCreateViewJson = ( meta: string, replace: boolean = false, ): JsonCreateGoogleSqlViewStatement => { - const { algorithm, sqlSecurity, withCheckOption } = GoogleSqlSquasher.unsquashView(meta); + const { sqlSecurity } = GoogleSqlSquasher.unsquashView(meta); return { type: 'googlesql_create_view', name: name, definition: definition, - algorithm, sqlSecurity, - withCheckOption, replace, }; }; diff --git a/drizzle-kit/src/schemaValidator.ts b/drizzle-kit/src/schemaValidator.ts index 81506fc03e..093be5fb84 100644 --- a/drizzle-kit/src/schemaValidator.ts +++ b/drizzle-kit/src/schemaValidator.ts @@ -1,9 +1,9 @@ import { enum as enumType, TypeOf, union } from 'zod'; +import { googlesqlSchemaSquashed } from './serializer/googlesqlSchema'; import { mysqlSchema, mysqlSchemaSquashed } from './serializer/mysqlSchema'; import { pgSchema, pgSchemaSquashed } from './serializer/pgSchema'; import { singlestoreSchema, singlestoreSchemaSquashed } from './serializer/singlestoreSchema'; import { sqliteSchema, SQLiteSchemaSquashed } from './serializer/sqliteSchema'; -import { googlesqlSchemaSquashed } from './serializer/googlesqlSchema'; export const dialects = ['postgresql', 'mysql', 'sqlite', 'turso', 'singlestore', 'gel', 'googlesql'] as const; export const dialect = enumType(dialects); diff --git a/drizzle-kit/src/serializer/googlesqlSchema.ts b/drizzle-kit/src/serializer/googlesqlSchema.ts index f73f294a17..52bb39fe6d 100644 --- a/drizzle-kit/src/serializer/googlesqlSchema.ts +++ b/drizzle-kit/src/serializer/googlesqlSchema.ts @@ -8,9 +8,6 @@ const index = object({ name: string(), columns: string().array(), isUnique: boolean(), - using: enumType(['btree', 'hash']).optional(), - algorithm: enumType(['default', 'inplace', 'copy']).optional(), - lock: enumType(['default', 'none', 'shared', 'exclusive']).optional(), }).strict(); const fk = object({ @@ -19,7 +16,6 @@ const fk = object({ columnsFrom: string().array(), tableTo: string(), columnsTo: string().array(), - onUpdate: string().optional(), onDelete: string().optional(), }).strict(); @@ -28,7 +24,6 @@ const column = object({ type: string(), primaryKey: boolean(), notNull: boolean(), - autoincrement: boolean().optional(), default: any().optional(), onUpdate: any().optional(), generated: object({ @@ -37,50 +32,27 @@ const column = object({ }).optional(), }).strict(); -const tableV3 = object({ - name: string(), - columns: record(string(), column), - indexes: record(string(), index), - foreignKeys: record(string(), fk), -}).strict(); - const compositePK = object({ name: string(), columns: string().array(), }).strict(); -const uniqueConstraint = object({ - name: string(), - columns: string().array(), -}).strict(); - const checkConstraint = object({ name: string(), value: string(), }).strict(); -const tableV4 = object({ - name: string(), - schema: string().optional(), - columns: record(string(), column), - indexes: record(string(), index), - foreignKeys: record(string(), fk), -}).strict(); - const table = object({ name: string(), columns: record(string(), column), indexes: record(string(), index), foreignKeys: record(string(), fk), compositePrimaryKeys: record(string(), compositePK), - uniqueConstraints: record(string(), uniqueConstraint).default({}), checkConstraint: record(string(), checkConstraint).default({}), }).strict(); const viewMeta = object({ - algorithm: enumType(['undefined', 'merge', 'temptable']), sqlSecurity: enumType(['definer', 'invoker']), - withCheckOption: enumType(['local', 'cascaded']).optional(), }).strict(); export const view = object({ @@ -120,103 +92,53 @@ const schemaHash = object({ prevId: string(), }); -export const schemaInternalV3 = object({ - version: literal('3'), - dialect: dialect, - tables: record(string(), tableV3), -}).strict(); - -export const schemaInternalV4 = object({ - version: literal('4'), - dialect: dialect, - tables: record(string(), tableV4), - schemas: record(string(), string()), -}).strict(); - -export const schemaInternalV5 = object({ - version: literal('5'), - dialect: dialect, - tables: record(string(), table), - schemas: record(string(), string()), - _meta: object({ - schemas: record(string(), string()), - tables: record(string(), string()), - columns: record(string(), string()), - }), - internal: kitInternals, -}).strict(); - export const schemaInternal = object({ - version: literal('5'), + version: literal('0'), dialect: dialect, tables: record(string(), table), + schemas: record(string(), string()), // TODO: SPANNER - verify views: record(string(), view).default({}), _meta: object({ tables: record(string(), string()), columns: record(string(), string()), + schemas: record(string(), string()), // TODO: SPANNER - verify }), internal: kitInternals, }).strict(); -export const schemaV3 = schemaInternalV3.merge(schemaHash); -export const schemaV4 = schemaInternalV4.merge(schemaHash); -export const schemaV5 = schemaInternalV5.merge(schemaHash); export const schema = schemaInternal.merge(schemaHash); -const tableSquashedV4 = object({ - name: string(), - schema: string().optional(), - columns: record(string(), column), - indexes: record(string(), string()), - foreignKeys: record(string(), string()), -}).strict(); - const tableSquashed = object({ name: string(), + schema: string().optional(), columns: record(string(), column), indexes: record(string(), string()), foreignKeys: record(string(), string()), compositePrimaryKeys: record(string(), string()), - uniqueConstraints: record(string(), string()).default({}), checkConstraints: record(string(), string()).default({}), }).strict(); const viewSquashed = view.omit({ - algorithm: true, sqlSecurity: true, - withCheckOption: true, }).extend({ meta: string() }); export const schemaSquashed = object({ - version: literal('5'), + version: literal('0'), dialect: dialect, tables: record(string(), tableSquashed), views: record(string(), viewSquashed), }).strict(); -export const schemaSquashedV4 = object({ - version: literal('4'), - dialect: dialect, - tables: record(string(), tableSquashedV4), - schemas: record(string(), string()), -}).strict(); - export type Dialect = TypeOf; export type Column = TypeOf; export type Table = TypeOf; -export type TableV4 = TypeOf; export type GoogleSqlSchema = TypeOf; -export type GoogleSqlSchemaV3 = TypeOf; -export type GoogleSqlSchemaV4 = TypeOf; -export type GoogleSqlSchemaV5 = TypeOf; export type GoogleSqlSchemaInternal = TypeOf; export type GoogleSqlKitInternals = TypeOf; export type GoogleSqlSchemaSquashed = TypeOf; -export type GoogleSqlSchemaSquashedV4 = TypeOf; export type Index = TypeOf; export type ForeignKey = TypeOf; export type PrimaryKey = TypeOf; -export type UniqueConstraint = TypeOf; export type CheckConstraint = TypeOf; export type View = TypeOf; export type ViewSquashed = TypeOf; @@ -224,19 +146,14 @@ export type ViewSquashed = TypeOf; export const GoogleSqlSquasher = { 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}`; }, unsquashIdx: (input: string): Index => { - const [name, columnsString, isUnique, using, algorithm, lock] = input.split(';'); + const [name, columnsString, isUnique] = input.split(';'); const destructed = { name, columns: columnsString.split(','), isUnique: isUnique === 'true', - using: using ? using : undefined, - algorithm: algorithm ? algorithm : undefined, - lock: lock ? lock : undefined, }; return index.parse(destructed); }, @@ -247,17 +164,10 @@ export const GoogleSqlSquasher = { const splitted = pk.split(';'); return { name: splitted[0], columns: splitted[1].split(',') }; }, - squashUnique: (unq: UniqueConstraint) => { - return `${unq.name};${unq.columns.join(',')}`; - }, - unsquashUnique: (unq: string): UniqueConstraint => { - const [name, columns] = unq.split(';'); - 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 ?? ''}`; + fk.onDelete ?? '' + }`; }, unsquashFK: (input: string): ForeignKey => { const [ @@ -266,7 +176,6 @@ export const GoogleSqlSquasher = { columnsFromStr, tableTo, columnsToStr, - onUpdate, onDelete, ] = input.split(';'); @@ -276,7 +185,6 @@ export const GoogleSqlSquasher = { columnsFrom: columnsFromStr.split(','), tableTo, columnsTo: columnsToStr.split(','), - onUpdate, onDelete, }); return result; @@ -290,53 +198,18 @@ export const GoogleSqlSquasher = { return { name, value }; }, squashView: (view: View): string => { - return `${view.algorithm};${view.sqlSecurity};${view.withCheckOption}`; + return `${view.sqlSecurity}`; }, unsquashView: (meta: string): SquasherViewMeta => { - const [algorithm, sqlSecurity, withCheckOption] = meta.split(';'); + const [sqlSecurity] = meta.split(';'); const toReturn = { - algorithm: algorithm, sqlSecurity: sqlSecurity, - withCheckOption: withCheckOption !== 'undefined' ? withCheckOption : undefined, }; return viewMeta.parse(toReturn); }, }; -export const squashGooglesqlSchemeV4 = ( - json: GoogleSqlSchemaV4, -): GoogleSqlSchemaSquashedV4 => { - const mappedTables = Object.fromEntries( - Object.entries(json.tables).map((it) => { - const squashedIndexes = mapValues(it[1].indexes, (index) => { - return GoogleSqlSquasher.squashIdx(index); - }); - - const squashedFKs = mapValues(it[1].foreignKeys, (fk) => { - return GoogleSqlSquasher.squashFK(fk); - }); - - return [ - it[0], - { - name: it[1].name, - schema: it[1].schema, - columns: it[1].columns, - indexes: squashedIndexes, - foreignKeys: squashedFKs, - }, - ]; - }), - ); - return { - version: '4', - dialect: json.dialect, - tables: mappedTables, - schemas: json.schemas, - }; -}; - export const squashGooglesqlScheme = (json: GoogleSqlSchema): GoogleSqlSchemaSquashed => { const mappedTables = Object.fromEntries( Object.entries(json.tables).map((it) => { @@ -352,13 +225,6 @@ export const squashGooglesqlScheme = (json: GoogleSqlSchema): GoogleSqlSchemaSqu return GoogleSqlSquasher.squashPK(pk); }); - const squashedUniqueConstraints = mapValues( - it[1].uniqueConstraints, - (unq) => { - return GoogleSqlSquasher.squashUnique(unq); - }, - ); - const squashedCheckConstraints = mapValues(it[1].checkConstraint, (check) => { return GoogleSqlSquasher.squashCheck(check); }); @@ -371,7 +237,6 @@ export const squashGooglesqlScheme = (json: GoogleSqlSchema): GoogleSqlSchemaSqu indexes: squashedIndexes, foreignKeys: squashedFKs, compositePrimaryKeys: squashedPKs, - uniqueConstraints: squashedUniqueConstraints, checkConstraints: squashedCheckConstraints, }, ]; @@ -393,7 +258,7 @@ export const squashGooglesqlScheme = (json: GoogleSqlSchema): GoogleSqlSchemaSqu ); return { - version: '5', + version: '0', dialect: json.dialect, tables: mappedTables, views: mappedViews, @@ -401,16 +266,13 @@ export const squashGooglesqlScheme = (json: GoogleSqlSchema): GoogleSqlSchemaSqu }; export const googlesqlSchema = schema; -export const googlesqlSchemaV3 = schemaV3; -export const googlesqlSchemaV4 = schemaV4; -export const googlesqlSchemaV5 = schemaV5; export const googlesqlSchemaSquashed = schemaSquashed; // no prev version -export const backwardCompatibleGooglesqlSchema = union([googlesqlSchemaV5, schema]); +export const backwardCompatibleGooglesqlSchema = union([googlesqlSchema, schema]); export const dryGoogleSql = googlesqlSchema.parse({ - version: '5', + version: '0', dialect: 'googlesql', id: originUUID, prevId: '', diff --git a/drizzle-kit/src/serializer/googlesqlSerializer.ts b/drizzle-kit/src/serializer/googlesqlSerializer.ts index af783d27e0..3db00a3215 100644 --- a/drizzle-kit/src/serializer/googlesqlSerializer.ts +++ b/drizzle-kit/src/serializer/googlesqlSerializer.ts @@ -8,9 +8,7 @@ import { GoogleSqlDialect, GoogleSqlView, type PrimaryKey as PrimaryKeyORM, - uniqueKeyName, } from 'drizzle-orm/googlesql'; -import { RowDataPacket } from 'mysql2/promise'; import { CasingType } from 'src/cli/validations/common'; import { withStyle } from '../cli/validations/outputs'; import { IntrospectStage, IntrospectStatus } from '../cli/views'; @@ -18,31 +16,20 @@ import { CheckConstraint, Column, ForeignKey, - Index, GoogleSqlKitInternals, GoogleSqlSchemaInternal, + Index, PrimaryKey, Table, - UniqueConstraint, View, } from '../serializer/googlesqlSchema'; -import { type DB, escapeSingleQuotes } from '../utils'; +import { type DB, escapeSingleQuotesGooglesql } from '../utils'; import { getColumnCasing, sqlToStr } from './utils'; -// TODO: SPANNER - verify - - export const indexName = (tableName: string, columns: string[]) => { return `${tableName}_${columns.join('_')}_index`; }; -const handleEnumType = (type: string) => { - let str = type.split('(')[1]; - str = str.substring(0, str.length - 1); - const values = str.split(',').map((v) => `'${escapeSingleQuotes(v.substring(1, v.length - 1))}'`); - return `enum(${values.join(',')})`; -}; - export const generateGoogleSqlSnapshot = ( tables: AnyGoogleSqlTable[], views: GoogleSqlView[], @@ -62,14 +49,12 @@ export const generateGoogleSqlSnapshot = ( schema, checks, primaryKeys, - uniqueConstraints, } = getTableConfig(table); const columnsObject: Record = {}; const indexesObject: Record = {}; const foreignKeysObject: Record = {}; const primaryKeysObject: Record = {}; - const uniqueConstraintObject: Record = {}; const checkConstraintObject: Record = {}; // this object will help to identify same check names @@ -80,27 +65,24 @@ export const generateGoogleSqlSnapshot = ( const notNull: boolean = column.notNull; const sqlType = column.getSQLType(); const sqlTypeLowered = sqlType.toLowerCase(); - const autoIncrement = typeof (column as any).autoIncrement === 'undefined' - ? false - : (column as any).autoIncrement; const generated = column.generated; const columnToSet: Column = { name, - type: sqlType.startsWith('enum') ? handleEnumType(sqlType) : sqlType, + type: sqlType, primaryKey: false, // If field is autoincrement it's notNull by default // notNull: autoIncrement ? true : notNull, notNull, - autoincrement: autoIncrement, + // autoincrement: false, onUpdate: (column as any).hasOnUpdateNow, generated: generated ? { as: is(generated.as, SQL) - ? dialect.sqlToQuery(generated.as as SQL).sql + ? dialect.sqlToQuery(generated.as as SQL, 'indexes').sql // TODO: SPANNER - should we create a new invoke source for this, instead of using "indexes"? maybe "generated"? : typeof generated.as === 'function' - ? dialect.sqlToQuery(generated.as() as SQL).sql + ? dialect.sqlToQuery(generated.as() as SQL, 'indexes').sql : (generated.as as any), type: generated.mode ?? 'stored', } @@ -115,36 +97,12 @@ export const generateGoogleSqlSnapshot = ( } if (column.isUnique) { - 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, - ) - } 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(','), - ) - } columns\n`) - }`, - ); - process.exit(1); - } - uniqueConstraintObject[column.uniqueName!] = { - name: column.uniqueName!, - columns: [columnToSet.name], - }; + console.log( + `\n${ + withStyle.errorWarning(`IsUnique is not supported in GoogleSQL. It's certainly a bug, please report it.`) + }`, + ); + process.exit(1); } if (column.default !== undefined) { @@ -152,16 +110,15 @@ export const generateGoogleSqlSnapshot = ( columnToSet.default = sqlToStr(column.default, casing); } else { if (typeof column.default === 'string') { - columnToSet.default = `'${escapeSingleQuotes(column.default)}'`; + columnToSet.default = `'${escapeSingleQuotesGooglesql(column.default)}'`; } else { if (sqlTypeLowered === 'json') { - columnToSet.default = `'${JSON.stringify(column.default)}'`; + columnToSet.default = `JSON '${JSON.stringify(column.default)}'`; } else if (column.default instanceof Date) { if (sqlTypeLowered === 'date') { columnToSet.default = `'${column.default.toISOString().split('T')[0]}'`; } else if ( - sqlTypeLowered.startsWith('datetime') - || sqlTypeLowered.startsWith('timestamp') + sqlTypeLowered.startsWith('timestamp') ) { columnToSet.default = `'${ column.default @@ -174,9 +131,8 @@ export const generateGoogleSqlSnapshot = ( columnToSet.default = column.default; } } - if (['blob', 'text', 'json'].includes(column.getSQLType())) { - columnToSet.default = `(${columnToSet.default})`; - } + + columnToSet.default = `(${columnToSet.default})`; } } columnsObject[name] = columnToSet; @@ -204,49 +160,9 @@ export const generateGoogleSqlSnapshot = ( } }); - uniqueConstraints?.map((unq) => { - const columnNames = unq.columns.map((c) => getColumnCasing(c, casing)); - - const name = unq.name ?? uniqueKeyName(table, columnNames); - - 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`, - ) - }`, - ); - process.exit(1); - } - - uniqueConstraintObject[name] = { - name: unq.name!, - columns: columnNames, - }; - }); - const fks: ForeignKey[] = foreignKeys.map((fk) => { const tableFrom = tableName; const onDelete = fk.onDelete ?? 'no action'; - const onUpdate = fk.onUpdate ?? 'no action'; const reference = fk.reference(); const referenceFT = reference.foreignTable; @@ -276,7 +192,6 @@ export const generateGoogleSqlSnapshot = ( columnsFrom, columnsTo, onDelete, - onUpdate, } as ForeignKey; }); @@ -314,62 +229,30 @@ export const generateGoogleSqlSnapshot = ( } }); - 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`, - ) - }`, - ); - process.exit(1); - } - } 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 - `, - ) - }`, - ); - process.exit(1); - } + if (typeof foreignKeysObject[name] !== 'undefined') { + console.log( + `\n${ + withStyle.errorWarning( + `In GoogleSQL, 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 ${ + // TODO: SPANNER - verify if this error message is correct + 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); } indexesObject[name] = { name, columns: indexColumns, isUnique: value.config.unique ?? false, - using: value.config.using, - algorithm: value.config.algorythm, - lock: value.config.lock, }; }); @@ -414,7 +297,6 @@ export const generateGoogleSqlSnapshot = ( indexes: indexesObject, foreignKeys: foreignKeysObject, compositePrimaryKeys: primaryKeysObject, - uniqueConstraints: uniqueConstraintObject, checkConstraint: checkConstraintObject, }; } @@ -427,9 +309,7 @@ export const generateGoogleSqlSnapshot = ( query, schema, selectedFields, - algorithm, sqlSecurity, - withCheckOption, } = getViewConfig(view); const columnsObject: Record = {}; @@ -456,9 +336,6 @@ export const generateGoogleSqlSnapshot = ( const notNull: boolean = column.notNull; const sqlTypeLowered = column.getSQLType().toLowerCase(); - const autoIncrement = typeof (column as any).autoIncrement === 'undefined' - ? false - : (column as any).autoIncrement; const generated = column.generated; @@ -469,14 +346,13 @@ export const generateGoogleSqlSnapshot = ( // If field is autoincrement it's notNull by default // notNull: autoIncrement ? true : notNull, notNull, - autoincrement: autoIncrement, onUpdate: (column as any).hasOnUpdateNow, generated: generated ? { as: is(generated.as, SQL) - ? dialect.sqlToQuery(generated.as as SQL).sql + ? dialect.sqlToQuery(generated.as as SQL, 'indexes').sql : typeof generated.as === 'function' - ? dialect.sqlToQuery(generated.as() as SQL).sql + ? dialect.sqlToQuery(generated.as() as SQL, 'indexes').sql : (generated.as as any), type: generated.mode ?? 'stored', } @@ -491,13 +367,12 @@ export const generateGoogleSqlSnapshot = ( columnToSet.default = `'${column.default}'`; } else { if (sqlTypeLowered === 'json') { - columnToSet.default = `'${JSON.stringify(column.default)}'`; + columnToSet.default = `JSON '${JSON.stringify(column.default)}'`; } else if (column.default instanceof Date) { if (sqlTypeLowered === 'date') { columnToSet.default = `'${column.default.toISOString().split('T')[0]}'`; } else if ( - sqlTypeLowered.startsWith('datetime') - || sqlTypeLowered.startsWith('timestamp') + sqlTypeLowered.startsWith('timestamp') ) { columnToSet.default = `'${ column.default @@ -510,9 +385,7 @@ export const generateGoogleSqlSnapshot = ( columnToSet.default = column.default; } } - if (['blob', 'text', 'json'].includes(column.getSQLType())) { - columnToSet.default = `(${columnToSet.default})`; - } + columnToSet.default = `(${columnToSet.default})`; } } columnsObject[column.name] = columnToSet; @@ -524,46 +397,25 @@ export const generateGoogleSqlSnapshot = ( name, isExisting, definition: isExisting ? undefined : dialect.sqlToQuery(query!).sql, - withCheckOption, - algorithm: algorithm ?? 'undefined', // set default values sqlSecurity: sqlSecurity ?? 'definer', // set default values }; } return { - version: '5', + version: '0', dialect: 'googlesql', tables: result, views: resultViews, + schemas: {}, // TODO: SPANNER - verify _meta: { tables: {}, columns: {}, + schemas: {}, // TODO: SPANNER - verify }, internal, }; }; -function clearDefaults(defaultValue: any, collate: string) { - if (typeof collate === 'undefined' || collate === null) { - collate = `utf8mb4`; - } - - let resultDefault = defaultValue; - collate = `_${collate}`; - if (defaultValue.startsWith(collate)) { - resultDefault = resultDefault - .substring(collate.length, defaultValue.length) - .replace(/\\/g, ''); - if (resultDefault.startsWith("'") && resultDefault.endsWith("'")) { - return `('${escapeSingleQuotes(resultDefault.substring(1, resultDefault.length - 1))}')`; - } else { - return `'${escapeSingleQuotes(resultDefault.substring(1, resultDefault.length - 1))}'`; - } - } else { - return `(${resultDefault})`; - } -} - export const fromDatabase = async ( db: DB, inputSchema: string, @@ -574,429 +426,5 @@ export const fromDatabase = async ( status: IntrospectStatus, ) => void, ): Promise => { - const result: Record = {}; - const internals: GoogleSqlKitInternals = { tables: {}, indexes: {} }; - - const columns = await db.query(`select * from information_schema.columns - where table_schema = '${inputSchema}' and table_name != '__drizzle_migrations' - order by table_name, ordinal_position;`); - - const response = columns as RowDataPacket[]; - - const schemas: string[] = []; - - let columnsCount = 0; - let tablesCount = new Set(); - let indexesCount = 0; - let foreignKeysCount = 0; - let checksCount = 0; - let viewsCount = 0; - - const idxs = await db.query( - `select * from INFORMATION_SCHEMA.STATISTICS - WHERE INFORMATION_SCHEMA.STATISTICS.TABLE_SCHEMA = '${inputSchema}' and INFORMATION_SCHEMA.STATISTICS.INDEX_NAME != 'PRIMARY';`, - ); - - const idxRows = idxs as RowDataPacket[]; - - for (const column of response) { - if (!tablesFilter(column['TABLE_NAME'] as string)) continue; - - columnsCount += 1; - if (progressCallback) { - progressCallback('columns', columnsCount, 'fetching'); - } - const schema: string = column['TABLE_SCHEMA']; - const tableName = column['TABLE_NAME']; - - tablesCount.add(`${schema}.${tableName}`); - if (progressCallback) { - progressCallback('columns', tablesCount.size, 'fetching'); - } - const columnName: string = column['COLUMN_NAME']; - const isNullable = column['IS_NULLABLE'] === 'YES'; // 'YES', 'NO' - const dataType = column['DATA_TYPE']; // varchar - const columnType = column['COLUMN_TYPE']; // varchar(256) - const isPrimary = column['COLUMN_KEY'] === 'PRI'; // 'PRI', '' - const columnDefault: string = column['COLUMN_DEFAULT']; - const collation: string = column['CHARACTER_SET_NAME']; - const geenratedExpression: string = column['GENERATION_EXPRESSION']; - - let columnExtra = column['EXTRA']; - let isAutoincrement = false; // 'auto_increment', '' - let isDefaultAnExpression = false; // 'auto_increment', '' - - if (typeof column['EXTRA'] !== 'undefined') { - columnExtra = column['EXTRA']; - isAutoincrement = column['EXTRA'] === 'auto_increment'; // 'auto_increment', '' - isDefaultAnExpression = column['EXTRA'].includes('DEFAULT_GENERATED'); // 'auto_increment', '' - } - - // if (isPrimary) { - // if (typeof tableToPk[tableName] === "undefined") { - // tableToPk[tableName] = [columnName]; - // } else { - // tableToPk[tableName].push(columnName); - // } - // } - - if (schema !== inputSchema) { - schemas.push(schema); - } - - const table = result[tableName]; - - // let changedType = columnType.replace("bigint unsigned", "serial") - let changedType = columnType; - - if (columnType === 'bigint unsigned' && !isNullable && isAutoincrement) { - // check unique here - const uniqueIdx = idxRows.filter( - (it) => - it['COLUMN_NAME'] === columnName - && it['TABLE_NAME'] === tableName - && it['NON_UNIQUE'] === 0, - ); - if (uniqueIdx && uniqueIdx.length === 1) { - changedType = columnType.replace('bigint unsigned', 'serial'); - } - } - - if (columnType.includes('decimal(10,0)')) { - changedType = columnType.replace('decimal(10,0)', 'decimal'); - } - - let onUpdate: boolean | undefined = undefined; - if ( - columnType.startsWith('timestamp') - && typeof columnExtra !== 'undefined' - && columnExtra.includes('on update CURRENT_TIMESTAMP') - ) { - onUpdate = true; - } - - const newColumn: Column = { - 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)}'`, - autoincrement: isAutoincrement, - name: columnName, - type: changedType, - primaryKey: false, - notNull: !isNullable, - onUpdate, - generated: geenratedExpression - ? { - as: geenratedExpression, - type: columnExtra === 'VIRTUAL GENERATED' ? 'virtual' : 'stored', - } - : undefined, - }; - - // Set default to internal object - if (isDefaultAnExpression) { - if (typeof internals!.tables![tableName] === 'undefined') { - internals!.tables![tableName] = { - columns: { - [columnName]: { - isDefaultAnExpression: true, - }, - }, - }; - } else { - if ( - typeof internals!.tables![tableName]!.columns[columnName] - === 'undefined' - ) { - internals!.tables![tableName]!.columns[columnName] = { - isDefaultAnExpression: true, - }; - } else { - internals!.tables![tableName]!.columns[ - columnName - ]!.isDefaultAnExpression = true; - } - } - } - - if (!table) { - result[tableName] = { - name: tableName, - columns: { - [columnName]: newColumn, - }, - compositePrimaryKeys: {}, - indexes: {}, - foreignKeys: {}, - uniqueConstraints: {}, - checkConstraint: {}, - }; - } else { - result[tableName]!.columns[columnName] = newColumn; - } - } - - const tablePks = await db.query( - `SELECT table_name, column_name, ordinal_position - FROM information_schema.table_constraints t - LEFT JOIN information_schema.key_column_usage k - USING(constraint_name,table_schema,table_name) - WHERE t.constraint_type='PRIMARY KEY' - and table_name != '__drizzle_migrations' - AND t.table_schema = '${inputSchema}' - ORDER BY ordinal_position`, - ); - - const tableToPk: { [tname: string]: string[] } = {}; - - const tableToPkRows = tablePks as RowDataPacket[]; - for (const tableToPkRow of tableToPkRows) { - const tableName: string = tableToPkRow['TABLE_NAME']; - const columnName: string = tableToPkRow['COLUMN_NAME']; - const position: string = tableToPkRow['ordinal_position']; - - if (typeof result[tableName] === 'undefined') { - continue; - } - - if (typeof tableToPk[tableName] === 'undefined') { - tableToPk[tableName] = [columnName]; - } else { - tableToPk[tableName].push(columnName); - } - } - - for (const [key, value] of Object.entries(tableToPk)) { - // if (value.length > 1) { - result[key].compositePrimaryKeys = { - [`${key}_${value.join('_')}`]: { - name: `${key}_${value.join('_')}`, - columns: value, - }, - }; - // } else if (value.length === 1) { - // result[key].columns[value[0]].primaryKey = true; - // } else { - // } - } - if (progressCallback) { - progressCallback('columns', columnsCount, 'done'); - progressCallback('tables', tablesCount.size, 'done'); - } - try { - const fks = await db.query( - `SELECT - kcu.TABLE_SCHEMA, - kcu.TABLE_NAME, - kcu.CONSTRAINT_NAME, - kcu.COLUMN_NAME, - kcu.REFERENCED_TABLE_SCHEMA, - kcu.REFERENCED_TABLE_NAME, - kcu.REFERENCED_COLUMN_NAME, - rc.UPDATE_RULE, - rc.DELETE_RULE - FROM - INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu - LEFT JOIN - information_schema.referential_constraints rc - ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME - WHERE kcu.TABLE_SCHEMA = '${inputSchema}' AND kcu.CONSTRAINT_NAME != 'PRIMARY' - AND kcu.REFERENCED_TABLE_NAME IS NOT NULL;`, - ); - - const fkRows = fks as RowDataPacket[]; - - for (const fkRow of fkRows) { - foreignKeysCount += 1; - if (progressCallback) { - progressCallback('fks', foreignKeysCount, 'fetching'); - } - const tableSchema = fkRow['TABLE_SCHEMA']; - const tableName: string = fkRow['TABLE_NAME']; - const constraintName = fkRow['CONSTRAINT_NAME']; - const columnName: string = fkRow['COLUMN_NAME']; - const refTableSchema = fkRow['REFERENCED_TABLE_SCHEMA']; - const refTableName = fkRow['REFERENCED_TABLE_NAME']; - const refColumnName: string = fkRow['REFERENCED_COLUMN_NAME']; - const updateRule: string = fkRow['UPDATE_RULE']; - const deleteRule = fkRow['DELETE_RULE']; - - const tableInResult = result[tableName]; - if (typeof tableInResult === 'undefined') continue; - - if (typeof tableInResult.foreignKeys[constraintName] !== 'undefined') { - tableInResult.foreignKeys[constraintName]!.columnsFrom.push(columnName); - tableInResult.foreignKeys[constraintName]!.columnsTo.push( - refColumnName, - ); - } else { - tableInResult.foreignKeys[constraintName] = { - name: constraintName, - tableFrom: tableName, - tableTo: refTableName, - columnsFrom: [columnName], - columnsTo: [refColumnName], - onDelete: deleteRule?.toLowerCase(), - onUpdate: updateRule?.toLowerCase(), - }; - } - - tableInResult.foreignKeys[constraintName]!.columnsFrom = [ - ...new Set(tableInResult.foreignKeys[constraintName]!.columnsFrom), - ]; - - tableInResult.foreignKeys[constraintName]!.columnsTo = [ - ...new Set(tableInResult.foreignKeys[constraintName]!.columnsTo), - ]; - } - } catch (e) { - // console.log(`Can't proccess foreign keys`); - } - if (progressCallback) { - progressCallback('fks', foreignKeysCount, 'done'); - } - - for (const idxRow of idxRows) { - const tableSchema = idxRow['TABLE_SCHEMA']; - const tableName = idxRow['TABLE_NAME']; - const constraintName = idxRow['INDEX_NAME']; - const columnName: string = idxRow['COLUMN_NAME']; - const isUnique = idxRow['NON_UNIQUE'] === 0; - - const tableInResult = result[tableName]; - if (typeof tableInResult === 'undefined') continue; - - // if (tableInResult.columns[columnName].type === "serial") continue; - - indexesCount += 1; - if (progressCallback) { - progressCallback('indexes', indexesCount, 'fetching'); - } - - if (isUnique) { - if ( - typeof tableInResult.uniqueConstraints[constraintName] !== 'undefined' - ) { - tableInResult.uniqueConstraints[constraintName]!.columns.push( - columnName, - ); - } else { - tableInResult.uniqueConstraints[constraintName] = { - name: constraintName, - columns: [columnName], - }; - } - } else { - // in MySQL FK creates index by default. Name of index is the same as fk constraint name - // so for introspect we will just skip it - if (typeof tableInResult.foreignKeys[constraintName] === 'undefined') { - if (typeof tableInResult.indexes[constraintName] !== 'undefined') { - tableInResult.indexes[constraintName]!.columns.push(columnName); - } else { - tableInResult.indexes[constraintName] = { - name: constraintName, - columns: [columnName], - isUnique: isUnique, - }; - } - } - } - } - - const views = await db.query( - `select * from INFORMATION_SCHEMA.VIEWS WHERE table_schema = '${inputSchema}';`, - ); - - const resultViews: Record = {}; - - viewsCount = views.length; - if (progressCallback) { - progressCallback('views', viewsCount, 'fetching'); - } - for await (const view of views) { - const viewName = view['TABLE_NAME']; - const definition = view['VIEW_DEFINITION']; - - const withCheckOption = view['CHECK_OPTION'] === 'NONE' ? undefined : view['CHECK_OPTION'].toLowerCase(); - const sqlSecurity = view['SECURITY_TYPE'].toLowerCase(); - - const [createSqlStatement] = await db.query(`SHOW CREATE VIEW \`${viewName}\`;`); - const algorithmMatch = createSqlStatement['Create View'].match(/ALGORITHM=([^ ]+)/); - const algorithm = algorithmMatch ? algorithmMatch[1].toLowerCase() : undefined; - - const columns = result[viewName].columns; - delete result[viewName]; - - resultViews[viewName] = { - columns: columns, - isExisting: false, - name: viewName, - algorithm, - definition, - sqlSecurity, - withCheckOption, - }; - } - - if (progressCallback) { - progressCallback('indexes', indexesCount, 'done'); - // progressCallback("enums", 0, "fetching"); - progressCallback('enums', 0, 'done'); - progressCallback('views', viewsCount, 'done'); - } - - const checkConstraints = await db.query( - `SELECT - tc.table_name, - tc.constraint_name, - cc.check_clause -FROM - information_schema.table_constraints tc -JOIN - information_schema.check_constraints cc - ON tc.constraint_name = cc.constraint_name -WHERE - tc.constraint_schema = '${inputSchema}' -AND - tc.constraint_type = 'CHECK';`, - ); - - checksCount += checkConstraints.length; - if (progressCallback) { - progressCallback('checks', checksCount, 'fetching'); - } - for (const checkConstraintRow of checkConstraints) { - const constraintName = checkConstraintRow['CONSTRAINT_NAME']; - const constraintValue = checkConstraintRow['CHECK_CLAUSE']; - const tableName = checkConstraintRow['TABLE_NAME']; - - const tableInResult = result[tableName]; - // if (typeof tableInResult === 'undefined') continue; - - tableInResult.checkConstraint[constraintName] = { - name: constraintName, - value: constraintValue, - }; - } - - if (progressCallback) { - progressCallback('checks', checksCount, 'done'); - } - - return { - version: '5', - dialect: 'googlesql', - tables: result, - views: resultViews, - _meta: { - tables: {}, - columns: {}, - }, - internal: internals, - }; + throw new Error('Not implemented.'); }; diff --git a/drizzle-kit/src/snapshotsDiffer.ts b/drizzle-kit/src/snapshotsDiffer.ts index 868b6abbc8..17dbc27424 100644 --- a/drizzle-kit/src/snapshotsDiffer.ts +++ b/drizzle-kit/src/snapshotsDiffer.ts @@ -21,6 +21,7 @@ import { _prepareSqliteAddColumns, JsonAddColumnStatement, JsonAlterCompositePK, + JsonAlterGoogleSqlViewStatement, JsonAlterIndPolicyStatement, JsonAlterMySqlViewStatement, JsonAlterPolicyStatement, @@ -29,6 +30,7 @@ import { JsonAlterViewStatement, JsonCreateCheckConstraint, JsonCreateCompositePK, + JsonCreateGoogleSqlViewStatement, JsonCreateIndPolicyStatement, JsonCreateMySqlViewStatement, JsonCreatePgViewStatement, @@ -59,6 +61,7 @@ import { prepareAddCompositePrimaryKeySqlite, prepareAddUniqueConstraintPg as prepareAddUniqueConstraint, prepareAddValuesToEnumJson, + prepareAlterColumnsGooglesql, prepareAlterColumnsMysql, prepareAlterCompositePrimaryKeyMySql, prepareAlterCompositePrimaryKeyPg, @@ -92,6 +95,9 @@ import { prepareDropSequenceJson, prepareDropTableJson, prepareDropViewJson, + prepareGoogleSqlAlterView, + prepareGoogleSqlCreateTableJson, + prepareGoogleSqlCreateViewJson, prepareLibSQLCreateReferencesJson, prepareLibSQLDropReferencesJson, prepareMoveEnumJson, @@ -121,19 +127,11 @@ import { prepareSqliteAlterColumns, prepareSQLiteCreateTable, prepareSqliteCreateViewJson, - prepareAddCompositePrimaryKeyGoogleSql, - prepareDeleteCompositePrimaryKeyGoogleSql, - prepareAlterCompositePrimaryKeyGoogleSql, - prepareAlterColumnsGooglesql, - prepareGoogleSqlCreateTableJson, - prepareGoogleSqlCreateViewJson, - JsonCreateGoogleSqlViewStatement, - prepareGoogleSqlAlterView, - JsonAlterGoogleSqlViewStatement, } from './jsonStatements'; import { Named, NamedWithSchema } from './cli/commands/migrate'; import { mapEntries, mapKeys, mapValues } from './global'; +import { GoogleSqlSchema, GoogleSqlSchemaSquashed } from './serializer/googlesqlSchema'; import { MySqlSchema, MySqlSchemaSquashed, MySqlSquasher, ViewSquashed } from './serializer/mysqlSchema'; import { mergedViewWithOption, @@ -152,7 +150,6 @@ import { SingleStoreSchema, SingleStoreSchemaSquashed, SingleStoreSquasher } fro import { SQLiteSchema, SQLiteSchemaSquashed, SQLiteSquasher, View as SqliteView } from './serializer/sqliteSchema'; import { libSQLCombineStatements, singleStoreCombineStatements, sqliteCombineStatements } from './statementCombiner'; import { copy, prepareMigrationMeta } from './utils'; -import { GoogleSqlSchema, GoogleSqlSchemaSquashed } from './serializer/googlesqlSchema'; const makeChanged = (schema: T) => { return object({ @@ -401,7 +398,6 @@ const alteredGoogleSqlViewSchema = alteredViewCommon.merge( }).strict(), ); - export const diffResultScheme = object({ alteredTablesWithColumns: alteredTableScheme.array(), alteredEnums: changedEnumSchema.array(), @@ -4353,33 +4349,33 @@ export const applyGooglesqlSnapshotsDiff = async ( // TODO: @AndriiSherman // Add an upgrade to v6 and move all snaphosts to this strcutre // After that we can generate mysql in 1 object directly(same as sqlite) - for (const tableName in json1.tables) { - const table = json1.tables[tableName]; - for (const indexName in table.indexes) { - const index = MySqlSquasher.unsquashIdx(table.indexes[indexName]); - if (index.isUnique) { - table.uniqueConstraints[indexName] = MySqlSquasher.squashUnique({ - name: index.name, - columns: index.columns, - }); - delete json1.tables[tableName].indexes[index.name]; - } - } - } - - for (const tableName in json2.tables) { - const table = json2.tables[tableName]; - for (const indexName in table.indexes) { - const index = MySqlSquasher.unsquashIdx(table.indexes[indexName]); - if (index.isUnique) { - table.uniqueConstraints[indexName] = MySqlSquasher.squashUnique({ - name: index.name, - columns: index.columns, - }); - delete json2.tables[tableName].indexes[index.name]; - } - } - } + // for (const tableName in json1.tables) { + // const table = json1.tables[tableName]; + // for (const indexName in table.indexes) { + // const index = MySqlSquasher.unsquashIdx(table.indexes[indexName]); + // if (index.isUnique) { + // table.uniqueConstraints[indexName] = MySqlSquasher.squashUnique({ + // name: index.name, + // columns: index.columns, + // }); + // delete json1.tables[tableName].indexes[index.name]; + // } + // } + // } + + // for (const tableName in json2.tables) { + // const table = json2.tables[tableName]; + // for (const indexName in table.indexes) { + // const index = MySqlSquasher.unsquashIdx(table.indexes[indexName]); + // if (index.isUnique) { + // table.uniqueConstraints[indexName] = MySqlSquasher.squashUnique({ + // name: index.name, + // columns: index.columns, + // }); + // delete json2.tables[tableName].indexes[index.name]; + // } + // } + // } const tablesDiff = diffSchemasOrTables(json1.tables, json2.tables); @@ -4578,63 +4574,44 @@ export const applyGooglesqlSnapshotsDiff = async ( // Don't need to sort, but need to add tests for it // addedColumns.sort(); // deletedColumns.sort(); - const doPerformDeleteAndCreate = JSON.stringify(addedColumns) !== JSON.stringify(deletedColumns); + // const doPerformDeleteAndCreate = JSON.stringify(addedColumns) !== JSON.stringify(deletedColumns); - let addedCompositePKs: JsonCreateCompositePK[] = []; - let deletedCompositePKs: JsonDeleteCompositePK[] = []; - let alteredCompositePKs: JsonAlterCompositePK[] = []; - - addedCompositePKs = prepareAddCompositePrimaryKeyGoogleSql( - it.name, - it.addedCompositePKs, - prevFull, - curFull, - ); - deletedCompositePKs = prepareDeleteCompositePrimaryKeyGoogleSql( - it.name, - it.deletedCompositePKs, - prevFull, - ); - // } - alteredCompositePKs = prepareAlterCompositePrimaryKeyGoogleSql( - it.name, - it.alteredCompositePKs, - prevFull, - curFull, - ); + // let addedCompositePKs: JsonCreateCompositePK[] = []; + // let deletedCompositePKs: JsonDeleteCompositePK[] = []; + // let alteredCompositePKs: JsonAlterCompositePK[] = []; // add logic for unique constraints - let addedUniqueConstraints: JsonCreateUniqueConstraint[] = []; - let deletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; - let alteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + // let addedUniqueConstraints: JsonCreateUniqueConstraint[] = []; + // let deletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; + // let alteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; let createdCheckConstraints: JsonCreateCheckConstraint[] = []; let deletedCheckConstraints: JsonDeleteCheckConstraint[] = []; - addedUniqueConstraints = prepareAddUniqueConstraint( - it.name, - it.schema, - it.addedUniqueConstraints, - ); - deletedUniqueConstraints = prepareDeleteUniqueConstraint( - it.name, - it.schema, - it.deletedUniqueConstraints, - ); - if (it.alteredUniqueConstraints) { - const added: Record = {}; - const deleted: Record = {}; - for (const k of Object.keys(it.alteredUniqueConstraints)) { - added[k] = it.alteredUniqueConstraints[k].__new; - deleted[k] = it.alteredUniqueConstraints[k].__old; - } - addedUniqueConstraints.push( - ...prepareAddUniqueConstraint(it.name, it.schema, added), - ); - deletedUniqueConstraints.push( - ...prepareDeleteUniqueConstraint(it.name, it.schema, deleted), - ); - } + // addedUniqueConstraints = prepareAddUniqueConstraint( + // it.name, + // it.schema, + // it.addedUniqueConstraints, + // ); + // deletedUniqueConstraints = prepareDeleteUniqueConstraint( + // it.name, + // it.schema, + // it.deletedUniqueConstraints, + // ); + // if (it.alteredUniqueConstraints) { + // const added: Record = {}; + // const deleted: Record = {}; + // for (const k of Object.keys(it.alteredUniqueConstraints)) { + // added[k] = it.alteredUniqueConstraints[k].__new; + // deleted[k] = it.alteredUniqueConstraints[k].__old; + // } + // addedUniqueConstraints.push( + // ...prepareAddUniqueConstraint(it.name, it.schema, added), + // ); + // deletedUniqueConstraints.push( + // ...prepareDeleteUniqueConstraint(it.name, it.schema, deleted), + // ); + // } createdCheckConstraints = prepareAddCheckConstraint(it.name, it.schema, it.addedCheckConstraints); deletedCheckConstraints = prepareDeleteCheckConstraint( @@ -4656,13 +4633,13 @@ export const applyGooglesqlSnapshotsDiff = async ( deletedCheckConstraints.push(...prepareDeleteCheckConstraint(it.name, it.schema, deleted)); } - jsonAddedCompositePKs.push(...addedCompositePKs); - jsonDeletedCompositePKs.push(...deletedCompositePKs); - jsonAlteredCompositePKs.push(...alteredCompositePKs); - - jsonAddedUniqueConstraints.push(...addedUniqueConstraints); - jsonDeletedUniqueConstraints.push(...deletedUniqueConstraints); - jsonAlteredUniqueConstraints.push(...alteredUniqueConstraints); + // all of it is not supported in google sql :) + // jsonAddedCompositePKs.push(...addedCompositePKs); + // jsonDeletedCompositePKs.push(...deletedCompositePKs); + // jsonAlteredCompositePKs.push(...alteredCompositePKs); + // jsonAddedUniqueConstraints.push(...addedUniqueConstraints); + // jsonDeletedUniqueConstraints.push(...deletedUniqueConstraints); + // jsonAlteredUniqueConstraints.push(...alteredUniqueConstraints); jsonCreatedCheckConstraints.push(...createdCheckConstraints); jsonDeletedCheckConstraints.push(...deletedCheckConstraints); diff --git a/drizzle-kit/src/utils.ts b/drizzle-kit/src/utils.ts index a1230e2198..85e8e43670 100644 --- a/drizzle-kit/src/utils.ts +++ b/drizzle-kit/src/utils.ts @@ -369,6 +369,10 @@ export function escapeSingleQuotes(str: string) { return str.replace(/'/g, "''"); } +export function escapeSingleQuotesGooglesql(str: string): string { + return str.replace(/'/g, "\\'"); +} + export function unescapeSingleQuotes(str: string, ignoreFirstAndLastChar: boolean) { const regex = ignoreFirstAndLastChar ? /(?