Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Drizzle-kit]: singlestore recreate #3950

Merged
merged 9 commits into from
Jan 23, 2025
1 change: 1 addition & 0 deletions drizzle-kit/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ export const pushSingleStoreSchema = async (
db,
statements,
validatedCur,
validatedPrev,
);

return {
Expand Down
27 changes: 3 additions & 24 deletions drizzle-kit/src/cli/commands/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,34 +208,16 @@ export const singlestorePush = async (
db,
filteredStatements,
statements.validatedCur,
statements.validatedPrev,
);

const filteredSqlStatements = fromJson(filteredStatements, 'singlestore');

const uniqueSqlStatementsToExecute: string[] = [];
statementsToExecute.forEach((ss) => {
if (!uniqueSqlStatementsToExecute.includes(ss)) {
uniqueSqlStatementsToExecute.push(ss);
}
});
const uniqueFilteredSqlStatements: string[] = [];
filteredSqlStatements.forEach((ss) => {
if (!uniqueFilteredSqlStatements.includes(ss)) {
uniqueFilteredSqlStatements.push(ss);
}
});

if (verbose) {
console.log();
console.log(
withStyle.warning('You are about to execute current statements:'),
);
console.log();
console.log(
[...uniqueSqlStatementsToExecute, ...uniqueFilteredSqlStatements]
.map((s) => chalk.blue(s))
.join('\n'),
);
console.log(statementsToExecute.map((s) => chalk.blue(s)).join('\n'));
console.log();
}

Expand Down Expand Up @@ -289,13 +271,10 @@ export const singlestorePush = async (
}
}

for (const dStmnt of uniqueSqlStatementsToExecute) {
for (const dStmnt of statementsToExecute) {
await db.query(dStmnt);
}

for (const statement of uniqueFilteredSqlStatements) {
await db.query(statement);
}
if (filteredStatements.length > 0) {
render(`[${chalk.green('✓')}] Changes applied`);
} else {
Expand Down
108 changes: 106 additions & 2 deletions drizzle-kit/src/cli/commands/singlestorePushUtils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import chalk from 'chalk';
import { render } from 'hanji';
import { fromJson } from 'src/sqlgenerator';
import { TypeOf } from 'zod';
import { JsonAlterColumnTypeStatement, JsonStatement } from '../../jsonStatements';
import { singlestoreSchema, SingleStoreSquasher } from '../../serializer/singlestoreSchema';
import type { DB } from '../../utils';
import { Column, SingleStoreSchemaSquashed, SingleStoreSquasher } from '../../serializer/singlestoreSchema';
import { singlestoreSchema } from '../../serializer/singlestoreSchema';
import { type DB, findAddedAndRemoved } from '../../utils';
import { Select } from '../selector-ui';
import { withStyle } from '../validations/outputs';

Expand Down Expand Up @@ -104,10 +106,30 @@ export const filterStatements = (
});
};

export function findColumnTypeAlternations(
columns1: Record<string, Column>,
columns2: Record<string, Column>,
): string[] {
const changes: string[] = [];

for (const key in columns1) {
if (columns1.hasOwnProperty(key) && columns2.hasOwnProperty(key)) {
const col1 = columns1[key];
const col2 = columns2[key];
if (col1.type !== col2.type) {
changes.push(col2.name);
}
}
}

return changes;
}

export const logSuggestionsAndReturn = async (
db: DB,
statements: JsonStatement[],
json2: TypeOf<typeof singlestoreSchema>,
json1: TypeOf<typeof singlestoreSchema>,
) => {
let shouldAskForApprove = false;
const statementsToExecute: string[] = [];
Expand Down Expand Up @@ -337,6 +359,88 @@ export const logSuggestionsAndReturn = async (
shouldAskForApprove = true;
}
}
} else if (statement.type === 'singlestore_recreate_table') {
const tableName = statement.tableName;

const prevColumns = json1.tables[tableName].columns;
const currentColumns = json2.tables[tableName].columns;
const { removedColumns, addedColumns } = findAddedAndRemoved(
Object.keys(prevColumns),
Object.keys(currentColumns),
);

if (removedColumns.length) {
for (const removedColumn of removedColumns) {
const res = await db.query<{ count: string }>(
`select count(\`${tableName}\`.\`${removedColumn}\`) as count from \`${tableName}\``,
);

const count = Number(res[0].count);
if (count > 0) {
infoToPrint.push(
`· You're about to delete ${
chalk.underline(
removedColumn,
)
} column in ${tableName} table with ${count} items`,
);
columnsToRemove.push(removedColumn);
shouldAskForApprove = true;
}
}
}

if (addedColumns.length) {
for (const addedColumn of addedColumns) {
const [res] = await db.query<{ count: string }>(
`select count(*) as count from \`${tableName}\``,
);

const columnConf = json2.tables[tableName].columns[addedColumn];

const count = Number(res.count);
if (count > 0 && columnConf.notNull && !columnConf.default) {
infoToPrint.push(
`· You're about to add not-null ${
chalk.underline(
addedColumn,
)
} column without default value to table, which contains ${count} items`,
);
shouldAskForApprove = true;
tablesToTruncate.push(tableName);

statementsToExecute.push(`TRUNCATE TABLE \`${tableName}\`;`);
}
}
}

const columnWithChangedType = findColumnTypeAlternations(prevColumns, currentColumns);
for (const column of columnWithChangedType) {
const [res] = await db.query<{ count: string }>(
`select count(*) as count from \`${tableName}\` WHERE \`${tableName}\`.\`${column}\` IS NOT NULL;`,
);

const count = Number(res.count);
if (count > 0) {
infoToPrint.push(
`· You're about recreate ${chalk.underline(tableName)} table with data type changing for ${
chalk.underline(
column,
)
} column, which contains ${count} items`,
);
shouldAskForApprove = true;
tablesToTruncate.push(tableName);

statementsToExecute.push(`TRUNCATE TABLE \`${tableName}\`;`);
}
}
}

const stmnt = fromJson([statement], 'singlestore', 'push');
if (typeof stmnt !== 'undefined') {
statementsToExecute.push(...stmnt);
}
}

Expand Down
9 changes: 9 additions & 0 deletions drizzle-kit/src/jsonStatements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ export interface JsonRecreateTableStatement {
checkConstraints: string[];
}

export interface JsonRecreateSingleStoreTableStatement {
type: 'singlestore_recreate_table';
tableName: string;
columns: Column[];
compositePKs: string[];
uniqueConstraints?: string[];
}

export interface JsonDropTableStatement {
type: 'drop_table';
tableName: string;
Expand Down Expand Up @@ -794,6 +802,7 @@ export type JsonAlterColumnStatement =
| JsonAlterColumnDropIdentityStatement;

export type JsonStatement =
| JsonRecreateSingleStoreTableStatement
| JsonRecreateTableStatement
| JsonAlterColumnStatement
| JsonCreateTableStatement
Expand Down
10 changes: 5 additions & 5 deletions drizzle-kit/src/snapshotsDiffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ import {
} from './serializer/pgSchema';
import { SingleStoreSchema, SingleStoreSchemaSquashed, SingleStoreSquasher } from './serializer/singlestoreSchema';
import { SQLiteSchema, SQLiteSchemaSquashed, SQLiteSquasher, View as SqliteView } from './serializer/sqliteSchema';
import { libSQLCombineStatements, sqliteCombineStatements } from './statementCombiner';
import { libSQLCombineStatements, singleStoreCombineStatements, sqliteCombineStatements } from './statementCombiner';
import { copy, prepareMigrationMeta } from './utils';

const makeChanged = <T extends ZodTypeAny>(schema: T) => {
Expand Down Expand Up @@ -2875,9 +2875,8 @@ export const applySingleStoreSnapshotsDiff = async (
return [viewKey, viewValue];
},
);

*/
const diffResult = applyJsonDiff(tablesPatchedSnap1, json2); // replace tablesPatchedSnap1 with viewsPatchedSnap1
const diffResult = applyJsonDiff(columnsPatchedSnap1, json2); // replace columnsPatchedSnap1 with viewsPatchedSnap1

const typedResult: DiffResultSingleStore = diffResultSchemeSingleStore.parse(diffResult);

Expand Down Expand Up @@ -3177,7 +3176,8 @@ export const applySingleStoreSnapshotsDiff = async (

jsonStatements.push(...jsonAlteredUniqueConstraints);

const sqlStatements = fromJson(jsonStatements, 'singlestore');
const combinedJsonStatements = singleStoreCombineStatements(jsonStatements, json2);
const sqlStatements = fromJson(combinedJsonStatements, 'singlestore');

const uniqueSqlStatements: string[] = [];
sqlStatements.forEach((ss) => {
Expand All @@ -3193,7 +3193,7 @@ export const applySingleStoreSnapshotsDiff = async (
const _meta = prepareMigrationMeta([], rTables, rColumns);

return {
statements: jsonStatements,
statements: combinedJsonStatements,
sqlStatements: uniqueSqlStatements,
_meta,
};
Expand Down
73 changes: 66 additions & 7 deletions drizzle-kit/src/sqlgenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {
JsonMoveEnumStatement,
JsonMoveSequenceStatement,
JsonPgCreateIndexStatement,
JsonRecreateSingleStoreTableStatement,
JsonRecreateTableStatement,
JsonRenameColumnStatement,
JsonRenameEnumStatement,
Expand Down Expand Up @@ -574,7 +575,7 @@ class MySqlCreateTableConvertor extends Convertor {
return statement;
}
}
class SingleStoreCreateTableConvertor extends Convertor {
export class SingleStoreCreateTableConvertor extends Convertor {
can(statement: JsonStatement, dialect: Dialect): boolean {
return statement.type === 'create_table' && dialect === 'singlestore';
}
Expand Down Expand Up @@ -618,7 +619,7 @@ class SingleStoreCreateTableConvertor extends Convertor {
if (typeof compositePKs !== 'undefined' && compositePKs.length > 0) {
statement += ',\n';
const compositePK = SingleStoreSquasher.unsquashPK(compositePKs[0]);
statement += `\tCONSTRAINT \`${st.compositePkName}\` PRIMARY KEY(\`${compositePK.columns.join(`\`,\``)}\`)`;
statement += `\tCONSTRAINT \`${compositePK.name}\` PRIMARY KEY(\`${compositePK.columns.join(`\`,\``)}\`)`;
}

if (
Expand Down Expand Up @@ -1531,7 +1532,7 @@ class MySQLDropTableConvertor extends Convertor {
}
}

class SingleStoreDropTableConvertor extends Convertor {
export class SingleStoreDropTableConvertor extends Convertor {
can(statement: JsonStatement, dialect: Dialect): boolean {
return statement.type === 'drop_table' && dialect === 'singlestore';
}
Expand Down Expand Up @@ -1590,14 +1591,14 @@ class MySqlRenameTableConvertor extends Convertor {
}
}

class SingleStoreRenameTableConvertor extends Convertor {
export class SingleStoreRenameTableConvertor extends Convertor {
can(statement: JsonStatement, dialect: Dialect): boolean {
return statement.type === 'rename_table' && dialect === 'singlestore';
}

convert(statement: JsonRenameTableStatement) {
const { tableNameFrom, tableNameTo } = statement;
return `RENAME TABLE \`${tableNameFrom}\` TO \`${tableNameTo}\`;`;
return `ALTER TABLE \`${tableNameFrom}\` RENAME TO \`${tableNameTo}\`;`;
}
}

Expand Down Expand Up @@ -1641,7 +1642,7 @@ class SingleStoreAlterTableRenameColumnConvertor extends Convertor {

convert(statement: JsonRenameColumnStatement) {
const { tableName, oldColumnName, newColumnName } = statement;
return `ALTER TABLE \`${tableName}\` RENAME COLUMN \`${oldColumnName}\` TO \`${newColumnName}\`;`;
return `ALTER TABLE \`${tableName}\` CHANGE \`${oldColumnName}\` \`${newColumnName}\`;`;
}
}

Expand Down Expand Up @@ -3499,7 +3500,7 @@ class CreateMySqlIndexConvertor extends Convertor {
}
}

class CreateSingleStoreIndexConvertor extends Convertor {
export class CreateSingleStoreIndexConvertor extends Convertor {
can(statement: JsonStatement, dialect: Dialect): boolean {
return statement.type === 'create_index' && dialect === 'singlestore';
}
Expand Down Expand Up @@ -3816,10 +3817,68 @@ class LibSQLRecreateTableConvertor extends Convertor {
}
}

class SingleStoreRecreateTableConvertor extends Convertor {
can(statement: JsonStatement, dialect: Dialect): boolean {
return (
statement.type === 'singlestore_recreate_table'
&& dialect === 'singlestore'
);
}

convert(statement: JsonRecreateSingleStoreTableStatement): string[] {
const { tableName, columns, compositePKs, uniqueConstraints } = statement;

const columnNames = columns.map((it) => `\`${it.name}\``).join(', ');
const newTableName = `__new_${tableName}`;

const sqlStatements: string[] = [];

// create new table
sqlStatements.push(
new SingleStoreCreateTableConvertor().convert({
type: 'create_table',
tableName: newTableName,
columns,
compositePKs,
uniqueConstraints,
schema: '',
}),
);

// migrate data
sqlStatements.push(
`INSERT INTO \`${newTableName}\`(${columnNames}) SELECT ${columnNames} FROM \`${tableName}\`;`,
);

// drop table
sqlStatements.push(
new SingleStoreDropTableConvertor().convert({
type: 'drop_table',
tableName: tableName,
schema: '',
}),
);

// rename table
sqlStatements.push(
new SingleStoreRenameTableConvertor().convert({
fromSchema: '',
tableNameFrom: newTableName,
tableNameTo: tableName,
toSchema: '',
type: 'rename_table',
}),
);

return sqlStatements;
}
}

const convertors: Convertor[] = [];
convertors.push(new PgCreateTableConvertor());
convertors.push(new MySqlCreateTableConvertor());
convertors.push(new SingleStoreCreateTableConvertor());
convertors.push(new SingleStoreRecreateTableConvertor());
convertors.push(new SQLiteCreateTableConvertor());
convertors.push(new SQLiteRecreateTableConvertor());
convertors.push(new LibSQLRecreateTableConvertor());
Expand Down
Loading
Loading