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

[SingleStore] Add SingleStore connector #32

Merged
merged 1 commit into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading