Skip to content

Commit

Permalink
[SingleStore] Add SingleStore connector (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rodriguespn authored Oct 2, 2024
1 parent c8359a1 commit 42758b9
Show file tree
Hide file tree
Showing 129 changed files with 32,368 additions and 107 deletions.
111 changes: 110 additions & 1 deletion drizzle-kit/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { randomUUID } from 'crypto';
import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
import { LibSQLDatabase } from 'drizzle-orm/libsql';
import type { MySql2Database } from 'drizzle-orm/mysql2';
import { PgDatabase } from 'drizzle-orm/pg-core';
import { SingleStoreDriverDatabase } from 'drizzle-orm/singlestore';
import {
columnsResolver,
enumsResolver,
Expand All @@ -22,12 +22,19 @@ import { generateMySqlSnapshot } from './serializer/mysqlSerializer';
import { prepareFromExports } from './serializer/pgImports';
import { PgSchema as PgSchemaKit, pgSchema, squashPgScheme } from './serializer/pgSchema';
import { generatePgSnapshot } from './serializer/pgSerializer';
import {
SingleStoreSchema as SingleStoreSchemaKit,
singlestoreSchema,
squashSingleStoreScheme,
} from './serializer/singlestoreSchema';
import { generateSingleStoreSnapshot } from './serializer/singlestoreSerializer';
import { SQLiteSchema as SQLiteSchemaKit, sqliteSchema, squashSqliteScheme } from './serializer/sqliteSchema';
import { generateSqliteSnapshot } from './serializer/sqliteSerializer';
import type { DB, SQLiteDB } from './utils';
export type DrizzleSnapshotJSON = PgSchemaKit;
export type DrizzleSQLiteSnapshotJSON = SQLiteSchemaKit;
export type DrizzleMySQLSnapshotJSON = MySQLSchemaKit;
export type DrizzleSingleStoreSnapshotJSON = SingleStoreSchemaKit;

export const generateDrizzleJson = (
imports: Record<string, unknown>,
Expand Down Expand Up @@ -340,6 +347,108 @@ export const pushMySQLSchema = async (
};
};

// SingleStore

export const generateSingleStoreDrizzleJson = async (
imports: Record<string, unknown>,
prevId?: string,
): Promise<SingleStoreSchemaKit> => {
const { prepareFromExports } = await import('./serializer/singlestoreImports');

const prepared = prepareFromExports(imports);

const id = randomUUID();

const snapshot = generateSingleStoreSnapshot(prepared.tables);

return {
...snapshot,
id,
prevId: prevId ?? originUUID,
};
};

export const generateSingleStoreMigration = async (
prev: DrizzleSingleStoreSnapshotJSON,
cur: DrizzleSingleStoreSnapshotJSON,
) => {
const { applySingleStoreSnapshotsDiff } = await import('./snapshotsDiffer');

const validatedPrev = singlestoreSchema.parse(prev);
const validatedCur = singlestoreSchema.parse(cur);

const squashedPrev = squashSingleStoreScheme(validatedPrev);
const squashedCur = squashSingleStoreScheme(validatedCur);

const { sqlStatements } = await applySingleStoreSnapshotsDiff(
squashedPrev,
squashedCur,
tablesResolver,
columnsResolver,
validatedPrev,
validatedCur,
);

return sqlStatements;
};

export const pushSingleStoreSchema = async (
imports: Record<string, unknown>,
drizzleInstance: SingleStoreDriverDatabase<any>,
databaseName: string,
) => {
const { applySingleStoreSnapshotsDiff } = await import('./snapshotsDiffer');
const { logSuggestionsAndReturn } = await import(
'./cli/commands/singlestorePushUtils'
);
const { singlestorePushIntrospect } = await import(
'./cli/commands/singlestoreIntrospect'
);
const { sql } = await import('drizzle-orm');

const db: DB = {
query: async (query: string, params?: any[]) => {
const res = await drizzleInstance.execute(sql.raw(query));
return res[0] as unknown as any[];
},
};
const cur = await generateSingleStoreDrizzleJson(imports);
const { schema: prev } = await singlestorePushIntrospect(db, databaseName, []);

const validatedPrev = singlestoreSchema.parse(prev);
const validatedCur = singlestoreSchema.parse(cur);

const squashedPrev = squashSingleStoreScheme(validatedPrev);
const squashedCur = squashSingleStoreScheme(validatedCur);

const { statements } = await applySingleStoreSnapshotsDiff(
squashedPrev,
squashedCur,
tablesResolver,
columnsResolver,
validatedPrev,
validatedCur,
'push',
);

const { shouldAskForApprove, statementsToExecute, infoToPrint } = await logSuggestionsAndReturn(
db,
statements,
validatedCur,
);

return {
hasDataLoss: shouldAskForApprove,
warnings: infoToPrint,
statementsToExecute,
apply: async () => {
for (const dStmnt of statementsToExecute) {
await db.query(dStmnt);
}
},
};
};

export const upPgSnapshot = (snapshot: Record<string, unknown>) => {
if (snapshot.version === '5') {
return upPgV7(upPgV6(snapshot));
Expand Down
112 changes: 108 additions & 4 deletions drizzle-kit/src/cli/commands/introspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,30 @@ import { render, renderWithTask } from 'hanji';
import { Minimatch } from 'minimatch';
import { join } from 'path';
import { plural, singular } from 'pluralize';
import { drySingleStore, SingleStoreSchema, squashSingleStoreScheme } from 'src/serializer/singlestoreSchema';
import { assertUnreachable, originUUID } from '../../global';
import { schemaToTypeScript as mysqlSchemaToTypeScript } from '../../introspect-mysql';
import { paramNameFor, schemaToTypeScript as postgresSchemaToTypeScript } from '../../introspect-pg';
import { schemaToTypeScript as singlestoreSchemaToTypeScript } from '../../introspect-singlestore';
import { schemaToTypeScript as sqliteSchemaToTypeScript } from '../../introspect-sqlite';
import { dryMySql, MySqlSchema, squashMysqlScheme } from '../../serializer/mysqlSchema';
import { fromDatabase as fromMysqlDatabase } from '../../serializer/mysqlSerializer';
import { dryPg, type PgSchema, squashPgScheme } from '../../serializer/pgSchema';
import { fromDatabase as fromPostgresDatabase } from '../../serializer/pgSerializer';
import { fromDatabase as fromSingleStoreDatabase } from '../../serializer/singlestoreSerializer';
import { drySQLite, type SQLiteSchema, squashSqliteScheme } from '../../serializer/sqliteSchema';
import { fromDatabase as fromSqliteDatabase } from '../../serializer/sqliteSerializer';
import { applyMysqlSnapshotsDiff, applyPgSnapshotsDiff, applySqliteSnapshotsDiff } from '../../snapshotsDiffer';
import {
applyMysqlSnapshotsDiff,
applyPgSnapshotsDiff,
applySingleStoreSnapshotsDiff,
applySqliteSnapshotsDiff,
} from '../../snapshotsDiffer';
import { prepareOutFolder } from '../../utils';
import type { Casing, Prefix } from '../validations/common';
import type { MysqlCredentials } from '../validations/mysql';
import type { PostgresCredentials } from '../validations/postgres';
import { SingleStoreCredentials } from '../validations/singlestore';
import type { SqliteCredentials } from '../validations/sqlite';
import { IntrospectProgress } from '../views';
import {
Expand Down Expand Up @@ -193,15 +202,14 @@ export const introspectMysql = async (
const schema = { id: originUUID, prevId: '', ...res } as MySqlSchema;
const ts = mysqlSchemaToTypeScript(schema, casing);
const relationsTs = relationsToTypeScript(schema, casing);
const { internal, ...schemaWithoutInternals } = schema;

const schemaFile = join(out, 'schema.ts');
writeFileSync(schemaFile, ts.file);
const relationsFile = join(out, 'relations.ts');
writeFileSync(relationsFile, relationsTs.file);
console.log();

const { snapshots, journal } = prepareOutFolder(out, 'postgresql');
const { snapshots, journal } = prepareOutFolder(out, 'mysql');

if (snapshots.length === 0) {
const { sqlStatements, _meta } = await applyMysqlSnapshotsDiff(
Expand Down Expand Up @@ -254,6 +262,102 @@ export const introspectMysql = async (
process.exit(0);
};

export const introspectSingleStore = async (
casing: Casing,
out: string,
breakpoints: boolean,
credentials: SingleStoreCredentials,
tablesFilter: string[],
prefix: Prefix,
) => {
const { connectToSingleStore } = await import('../connections');
const { db, database } = await connectToSingleStore(credentials);

const matchers = tablesFilter.map((it) => {
return new Minimatch(it);
});

const filter = (tableName: string) => {
if (matchers.length === 0) return true;

let flags: boolean[] = [];

for (let matcher of matchers) {
if (matcher.negate) {
if (!matcher.match(tableName)) {
flags.push(false);
}
}

if (matcher.match(tableName)) {
flags.push(true);
}
}

if (flags.length > 0) {
return flags.every(Boolean);
}
return false;
};

const progress = new IntrospectProgress();
const res = await renderWithTask(
progress,
fromSingleStoreDatabase(db, database, filter, (stage, count, status) => {
progress.update(stage, count, status);
}),
);

const schema = { id: originUUID, prevId: '', ...res } as SingleStoreSchema;
const ts = singlestoreSchemaToTypeScript(schema, casing);
const { internal, ...schemaWithoutInternals } = schema;

const schemaFile = join(out, 'schema.ts');
writeFileSync(schemaFile, ts.file);
console.log();

const { snapshots, journal } = prepareOutFolder(out, 'postgresql');

if (snapshots.length === 0) {
const { sqlStatements, _meta } = await applySingleStoreSnapshotsDiff(
squashSingleStoreScheme(drySingleStore),
squashSingleStoreScheme(schema),
tablesResolver,
columnsResolver,
drySingleStore,
schema,
);

writeResult({
cur: schema,
sqlStatements,
journal,
_meta,
outFolder: out,
breakpoints,
type: 'introspect',
prefixMode: prefix,
});
} else {
render(
`[${
chalk.blue(
'i',
)
}] No SQL generated, you already have migrations in project`,
);
}

render(
`[${
chalk.green(
'✓',
)
}] You schema file is ready ➜ ${chalk.bold.underline.blue(schemaFile)} 🚀`,
);
process.exit(0);
};

export const introspectSqlite = async (
casing: Casing,
out: string,
Expand Down Expand Up @@ -312,7 +416,7 @@ export const introspectSqlite = async (
writeFileSync(relationsFile, relationsTs.file);
console.log();

const { snapshots, journal } = prepareOutFolder(out, 'postgresql');
const { snapshots, journal } = prepareOutFolder(out, 'sqlite');

if (snapshots.length === 0) {
const { sqlStatements, _meta } = await applySqliteSnapshotsDiff(
Expand Down
Loading

0 comments on commit 42758b9

Please sign in to comment.