Files
drizzle-team--drizzle-orm/drizzle-kit/tests/schemaDiffer.ts
T
2026-07-13 12:10:05 +08:00

2785 lines
64 KiB
TypeScript

import { PGlite } from '@electric-sql/pglite';
import { Client } from '@libsql/client/.';
import { Database } from 'better-sqlite3';
import { is } from 'drizzle-orm';
import { MySqlSchema, MySqlTable, MySqlView } from 'drizzle-orm/mysql-core';
import {
getMaterializedViewConfig,
isPgEnum,
isPgMaterializedView,
isPgSequence,
isPgView,
PgEnum,
PgEnumObject,
PgMaterializedView,
PgPolicy,
PgRole,
PgSchema,
PgSequence,
PgTable,
PgView,
} from 'drizzle-orm/pg-core';
import { SingleStoreSchema, SingleStoreTable } from 'drizzle-orm/singlestore-core';
import { SQLiteTable, SQLiteView } from 'drizzle-orm/sqlite-core';
import * as fs from 'fs';
import { type Client as GelClient } from 'gel';
import { Connection } from 'mysql2/promise';
import { libSqlLogSuggestionsAndReturn } from 'src/cli/commands/libSqlPushUtils';
import {
columnsResolver,
enumsResolver,
indPolicyResolver,
mySqlViewsResolver,
Named,
policyResolver,
roleResolver,
schemasResolver,
sequencesResolver,
sqliteViewsResolver,
tablesResolver,
viewsResolver,
} from 'src/cli/commands/migrate';
import { pgSuggestions } from 'src/cli/commands/pgPushUtils';
import { logSuggestionsAndReturn as singleStoreLogSuggestionsAndReturn } from 'src/cli/commands/singlestorePushUtils';
import { logSuggestionsAndReturn } from 'src/cli/commands/sqlitePushUtils';
import { Entities } from 'src/cli/validations/cli';
import { CasingType } from 'src/cli/validations/common';
import { schemaToTypeScript as schemaToTypeScriptGel } from 'src/introspect-gel';
import { schemaToTypeScript as schemaToTypeScriptMySQL } from 'src/introspect-mysql';
import { schemaToTypeScript } from 'src/introspect-pg';
import { schemaToTypeScript as schemaToTypeScriptSingleStore } from 'src/introspect-singlestore';
import { schemaToTypeScript as schemaToTypeScriptSQLite } from 'src/introspect-sqlite';
import { fromDatabase as fromGelDatabase } from 'src/serializer/gelSerializer';
import { prepareFromMySqlImports } from 'src/serializer/mysqlImports';
import { mysqlSchema, squashMysqlScheme, ViewSquashed } from 'src/serializer/mysqlSchema';
import { fromDatabase as fromMySqlDatabase, generateMySqlSnapshot } from 'src/serializer/mysqlSerializer';
import { prepareFromPgImports } from 'src/serializer/pgImports';
import { pgSchema, Policy, Role, squashPgScheme, View } from 'src/serializer/pgSchema';
import { fromDatabase, generatePgSnapshot } from 'src/serializer/pgSerializer';
import { prepareFromSingleStoreImports } from 'src/serializer/singlestoreImports';
import { singlestoreSchema, squashSingleStoreScheme } from 'src/serializer/singlestoreSchema';
import {
fromDatabase as fromSingleStoreDatabase,
generateSingleStoreSnapshot,
} from 'src/serializer/singlestoreSerializer';
import { prepareFromSqliteImports } from 'src/serializer/sqliteImports';
import { sqliteSchema, squashSqliteScheme, View as SqliteView } from 'src/serializer/sqliteSchema';
import { fromDatabase as fromSqliteDatabase, generateSqliteSnapshot } from 'src/serializer/sqliteSerializer';
import {
applyLibSQLSnapshotsDiff,
applyMysqlSnapshotsDiff,
applyPgSnapshotsDiff,
applySingleStoreSnapshotsDiff,
applySqliteSnapshotsDiff,
Column,
ColumnsResolverInput,
ColumnsResolverOutput,
Enum,
PolicyResolverInput,
PolicyResolverOutput,
ResolverInput,
ResolverOutput,
ResolverOutputWithMoved,
RolesResolverInput,
RolesResolverOutput,
Sequence,
Table,
TablePolicyResolverInput,
TablePolicyResolverOutput,
} from 'src/snapshotsDiffer';
export type PostgresSchema = Record<
string,
| PgTable<any>
| PgEnum<any>
| PgEnumObject<any>
| PgSchema
| PgSequence
| PgView
| PgMaterializedView
| PgRole
| PgPolicy
>;
export type MysqlSchema = Record<
string,
MySqlTable<any> | MySqlSchema | MySqlView
>;
export type SqliteSchema = Record<string, SQLiteTable<any> | SQLiteView>;
export type SinglestoreSchema = Record<
string,
SingleStoreTable<any> | SingleStoreSchema /* | SingleStoreView */
>;
export const testSchemasResolver =
(renames: Set<string>) => async (input: ResolverInput<Named>): Promise<ResolverOutput<Named>> => {
try {
if (
input.created.length === 0
|| input.deleted.length === 0
|| renames.size === 0
) {
return {
created: input.created,
renamed: [],
deleted: input.deleted,
};
}
let createdSchemas = [...input.created];
let deletedSchemas = [...input.deleted];
const result: {
created: Named[];
renamed: { from: Named; to: Named }[];
deleted: Named[];
} = { created: [], renamed: [], deleted: [] };
for (let rename of renames) {
const [from, to] = rename.split('->');
const idxFrom = deletedSchemas.findIndex((it) => {
return it.name === from;
});
if (idxFrom >= 0) {
const idxTo = createdSchemas.findIndex((it) => {
return it.name === to;
});
result.renamed.push({
from: deletedSchemas[idxFrom],
to: createdSchemas[idxTo],
});
delete createdSchemas[idxTo];
delete deletedSchemas[idxFrom];
createdSchemas = createdSchemas.filter(Boolean);
deletedSchemas = deletedSchemas.filter(Boolean);
}
}
result.created = createdSchemas;
result.deleted = deletedSchemas;
return result;
} catch (e) {
console.error(e);
throw e;
}
};
export const testSequencesResolver = (renames: Set<string>) =>
async (
input: ResolverInput<Sequence>,
): Promise<ResolverOutputWithMoved<Sequence>> => {
try {
if (
input.created.length === 0
|| input.deleted.length === 0
|| renames.size === 0
) {
return {
created: input.created,
moved: [],
renamed: [],
deleted: input.deleted,
};
}
let createdSequences = [...input.created];
let deletedSequences = [...input.deleted];
const result: {
created: Sequence[];
moved: { name: string; schemaFrom: string; schemaTo: string }[];
renamed: { from: Sequence; to: Sequence }[];
deleted: Sequence[];
} = { created: [], renamed: [], deleted: [], moved: [] };
for (let rename of renames) {
const [from, to] = rename.split('->');
const idxFrom = deletedSequences.findIndex((it) => {
return `${it.schema || 'public'}.${it.name}` === from;
});
if (idxFrom >= 0) {
const idxTo = createdSequences.findIndex((it) => {
return `${it.schema || 'public'}.${it.name}` === to;
});
const tableFrom = deletedSequences[idxFrom];
const tableTo = createdSequences[idxFrom];
if (tableFrom.schema !== tableTo.schema) {
result.moved.push({
name: tableFrom.name,
schemaFrom: tableFrom.schema,
schemaTo: tableTo.schema,
});
}
if (tableFrom.name !== tableTo.name) {
result.renamed.push({
from: deletedSequences[idxFrom],
to: createdSequences[idxTo],
});
}
delete createdSequences[idxTo];
delete deletedSequences[idxFrom];
createdSequences = createdSequences.filter(Boolean);
deletedSequences = deletedSequences.filter(Boolean);
}
}
result.created = createdSequences;
result.deleted = deletedSequences;
return result;
} catch (e) {
console.error(e);
throw e;
}
};
export const testEnumsResolver = (renames: Set<string>) =>
async (
input: ResolverInput<Enum>,
): Promise<ResolverOutputWithMoved<Enum>> => {
try {
if (
input.created.length === 0
|| input.deleted.length === 0
|| renames.size === 0
) {
return {
created: input.created,
moved: [],
renamed: [],
deleted: input.deleted,
};
}
let createdEnums = [...input.created];
let deletedEnums = [...input.deleted];
const result: {
created: Enum[];
moved: { name: string; schemaFrom: string; schemaTo: string }[];
renamed: { from: Enum; to: Enum }[];
deleted: Enum[];
} = { created: [], renamed: [], deleted: [], moved: [] };
for (let rename of renames) {
const [from, to] = rename.split('->');
const idxFrom = deletedEnums.findIndex((it) => {
return `${it.schema || 'public'}.${it.name}` === from;
});
if (idxFrom >= 0) {
const idxTo = createdEnums.findIndex((it) => {
return `${it.schema || 'public'}.${it.name}` === to;
});
const tableFrom = deletedEnums[idxFrom];
const tableTo = createdEnums[idxFrom];
if (tableFrom.schema !== tableTo.schema) {
result.moved.push({
name: tableFrom.name,
schemaFrom: tableFrom.schema,
schemaTo: tableTo.schema,
});
}
if (tableFrom.name !== tableTo.name) {
result.renamed.push({
from: deletedEnums[idxFrom],
to: createdEnums[idxTo],
});
}
delete createdEnums[idxTo];
delete deletedEnums[idxFrom];
createdEnums = createdEnums.filter(Boolean);
deletedEnums = deletedEnums.filter(Boolean);
}
}
result.created = createdEnums;
result.deleted = deletedEnums;
return result;
} catch (e) {
console.error(e);
throw e;
}
};
export const testTablesResolver = (renames: Set<string>) =>
async (
input: ResolverInput<Table>,
): Promise<ResolverOutputWithMoved<Table>> => {
try {
if (
input.created.length === 0
|| input.deleted.length === 0
|| renames.size === 0
) {
return {
created: input.created,
moved: [],
renamed: [],
deleted: input.deleted,
};
}
let createdTables = [...input.created];
let deletedTables = [...input.deleted];
const result: {
created: Table[];
moved: { name: string; schemaFrom: string; schemaTo: string }[];
renamed: { from: Table; to: Table }[];
deleted: Table[];
} = { created: [], renamed: [], deleted: [], moved: [] };
for (let rename of renames) {
const [from, to] = rename.split('->');
const idxFrom = deletedTables.findIndex((it) => {
return `${it.schema || 'public'}.${it.name}` === from;
});
if (idxFrom >= 0) {
const idxTo = createdTables.findIndex((it) => {
return `${it.schema || 'public'}.${it.name}` === to;
});
const tableFrom = deletedTables[idxFrom];
const tableTo = createdTables[idxFrom];
if (tableFrom.schema !== tableTo.schema) {
result.moved.push({
name: tableFrom.name,
schemaFrom: tableFrom.schema,
schemaTo: tableTo.schema,
});
}
if (tableFrom.name !== tableTo.name) {
result.renamed.push({
from: deletedTables[idxFrom],
to: createdTables[idxTo],
});
}
delete createdTables[idxTo];
delete deletedTables[idxFrom];
createdTables = createdTables.filter(Boolean);
deletedTables = deletedTables.filter(Boolean);
}
}
result.created = createdTables;
result.deleted = deletedTables;
return result;
} catch (e) {
console.error(e);
throw e;
}
};
export const testColumnsResolver = (renames: Set<string>) =>
async (
input: ColumnsResolverInput<Column>,
): Promise<ColumnsResolverOutput<Column>> => {
try {
if (
input.created.length === 0
|| input.deleted.length === 0
|| renames.size === 0
) {
return {
tableName: input.tableName,
schema: input.schema,
created: input.created,
renamed: [],
deleted: input.deleted,
};
}
let createdColumns = [...input.created];
let deletedColumns = [...input.deleted];
const renamed: { from: Column; to: Column }[] = [];
const schema = input.schema || 'public';
for (let rename of renames) {
const [from, to] = rename.split('->');
const idxFrom = deletedColumns.findIndex((it) => {
return `${schema}.${input.tableName}.${it.name}` === from;
});
if (idxFrom >= 0) {
const idxTo = createdColumns.findIndex((it) => {
return `${schema}.${input.tableName}.${it.name}` === to;
});
renamed.push({
from: deletedColumns[idxFrom],
to: createdColumns[idxTo],
});
delete createdColumns[idxTo];
delete deletedColumns[idxFrom];
createdColumns = createdColumns.filter(Boolean);
deletedColumns = deletedColumns.filter(Boolean);
}
}
return {
tableName: input.tableName,
schema: input.schema,
created: createdColumns,
deleted: deletedColumns,
renamed,
};
} catch (e) {
console.error(e);
throw e;
}
};
export const testPolicyResolver = (renames: Set<string>) =>
async (
input: TablePolicyResolverInput<Policy>,
): Promise<TablePolicyResolverOutput<Policy>> => {
try {
if (
input.created.length === 0
|| input.deleted.length === 0
|| renames.size === 0
) {
return {
tableName: input.tableName,
schema: input.schema,
created: input.created,
renamed: [],
deleted: input.deleted,
};
}
let createdPolicies = [...input.created];
let deletedPolicies = [...input.deleted];
const renamed: { from: Policy; to: Policy }[] = [];
const schema = input.schema || 'public';
for (let rename of renames) {
const [from, to] = rename.split('->');
const idxFrom = deletedPolicies.findIndex((it) => {
return `${schema}.${input.tableName}.${it.name}` === from;
});
if (idxFrom >= 0) {
const idxTo = createdPolicies.findIndex((it) => {
return `${schema}.${input.tableName}.${it.name}` === to;
});
renamed.push({
from: deletedPolicies[idxFrom],
to: createdPolicies[idxTo],
});
delete createdPolicies[idxTo];
delete deletedPolicies[idxFrom];
createdPolicies = createdPolicies.filter(Boolean);
deletedPolicies = deletedPolicies.filter(Boolean);
}
}
return {
tableName: input.tableName,
schema: input.schema,
created: createdPolicies,
deleted: deletedPolicies,
renamed,
};
} catch (e) {
console.error(e);
throw e;
}
};
export const testIndPolicyResolver = (renames: Set<string>) =>
async (
input: PolicyResolverInput<Policy>,
): Promise<PolicyResolverOutput<Policy>> => {
try {
if (
input.created.length === 0
|| input.deleted.length === 0
|| renames.size === 0
) {
return {
created: input.created,
renamed: [],
deleted: input.deleted,
};
}
let createdPolicies = [...input.created];
let deletedPolicies = [...input.deleted];
const renamed: { from: Policy; to: Policy }[] = [];
for (let rename of renames) {
const [from, to] = rename.split('->');
const idxFrom = deletedPolicies.findIndex((it) => {
return `${it.on}.${it.name}` === from;
});
if (idxFrom >= 0) {
const idxTo = createdPolicies.findIndex((it) => {
return `${it.on}.${it.name}` === to;
});
renamed.push({
from: deletedPolicies[idxFrom],
to: createdPolicies[idxTo],
});
delete createdPolicies[idxTo];
delete deletedPolicies[idxFrom];
createdPolicies = createdPolicies.filter(Boolean);
deletedPolicies = deletedPolicies.filter(Boolean);
}
}
return {
created: createdPolicies,
deleted: deletedPolicies,
renamed,
};
} catch (e) {
console.error(e);
throw e;
}
};
export const testRolesResolver = (renames: Set<string>) =>
async (
input: RolesResolverInput<Role>,
): Promise<RolesResolverOutput<Role>> => {
try {
if (
input.created.length === 0
|| input.deleted.length === 0
|| renames.size === 0
) {
return {
created: input.created,
renamed: [],
deleted: input.deleted,
};
}
let createdPolicies = [...input.created];
let deletedPolicies = [...input.deleted];
const renamed: { from: Policy; to: Policy }[] = [];
for (let rename of renames) {
const [from, to] = rename.split('->');
const idxFrom = deletedPolicies.findIndex((it) => {
return `${it.name}` === from;
});
if (idxFrom >= 0) {
const idxTo = createdPolicies.findIndex((it) => {
return `${it.name}` === to;
});
renamed.push({
from: deletedPolicies[idxFrom],
to: createdPolicies[idxTo],
});
delete createdPolicies[idxTo];
delete deletedPolicies[idxFrom];
createdPolicies = createdPolicies.filter(Boolean);
deletedPolicies = deletedPolicies.filter(Boolean);
}
}
return {
created: createdPolicies,
deleted: deletedPolicies,
renamed,
};
} catch (e) {
console.error(e);
throw e;
}
};
export const testViewsResolver = (renames: Set<string>) =>
async (
input: ResolverInput<View>,
): Promise<ResolverOutputWithMoved<View>> => {
try {
if (
input.created.length === 0
|| input.deleted.length === 0
|| renames.size === 0
) {
return {
created: input.created,
moved: [],
renamed: [],
deleted: input.deleted,
};
}
let createdViews = [...input.created];
let deletedViews = [...input.deleted];
const result: {
created: View[];
moved: { name: string; schemaFrom: string; schemaTo: string }[];
renamed: { from: View; to: View }[];
deleted: View[];
} = { created: [], renamed: [], deleted: [], moved: [] };
for (let rename of renames) {
const [from, to] = rename.split('->');
const idxFrom = deletedViews.findIndex((it) => {
return `${it.schema || 'public'}.${it.name}` === from;
});
if (idxFrom >= 0) {
const idxTo = createdViews.findIndex((it) => {
return `${it.schema || 'public'}.${it.name}` === to;
});
const viewFrom = deletedViews[idxFrom];
const viewTo = createdViews[idxFrom];
if (viewFrom.schema !== viewTo.schema) {
result.moved.push({
name: viewFrom.name,
schemaFrom: viewFrom.schema,
schemaTo: viewTo.schema,
});
}
if (viewFrom.name !== viewTo.name) {
result.renamed.push({
from: deletedViews[idxFrom],
to: createdViews[idxTo],
});
}
delete createdViews[idxTo];
delete deletedViews[idxFrom];
createdViews = createdViews.filter(Boolean);
deletedViews = deletedViews.filter(Boolean);
}
}
result.created = createdViews;
result.deleted = deletedViews;
return result;
} catch (e) {
console.error(e);
throw e;
}
};
export const testViewsResolverMySql = (renames: Set<string>) =>
async (
input: ResolverInput<ViewSquashed & { schema: '' }>,
): Promise<ResolverOutputWithMoved<ViewSquashed>> => {
try {
if (
input.created.length === 0
|| input.deleted.length === 0
|| renames.size === 0
) {
return {
created: input.created,
moved: [],
renamed: [],
deleted: input.deleted,
};
}
let createdViews = [...input.created];
let deletedViews = [...input.deleted];
const result: {
created: ViewSquashed[];
moved: { name: string; schemaFrom: string; schemaTo: string }[];
renamed: { from: ViewSquashed; to: ViewSquashed }[];
deleted: ViewSquashed[];
} = { created: [], renamed: [], deleted: [], moved: [] };
for (let rename of renames) {
const [from, to] = rename.split('->');
const idxFrom = deletedViews.findIndex((it) => {
return `${it.schema || 'public'}.${it.name}` === from;
});
if (idxFrom >= 0) {
const idxTo = createdViews.findIndex((it) => {
return `${it.schema || 'public'}.${it.name}` === to;
});
const viewFrom = deletedViews[idxFrom];
const viewTo = createdViews[idxFrom];
if (viewFrom.schema !== viewTo.schema) {
result.moved.push({
name: viewFrom.name,
schemaFrom: viewFrom.schema,
schemaTo: viewTo.schema,
});
}
if (viewFrom.name !== viewTo.name) {
result.renamed.push({
from: deletedViews[idxFrom],
to: createdViews[idxTo],
});
}
delete createdViews[idxTo];
delete deletedViews[idxFrom];
createdViews = createdViews.filter(Boolean);
deletedViews = deletedViews.filter(Boolean);
}
}
result.created = createdViews;
result.deleted = deletedViews;
return result;
} catch (e) {
console.error(e);
throw e;
}
};
export const testViewsResolverSingleStore = (renames: Set<string>) =>
async (
input: ResolverInput<ViewSquashed & { schema: '' }>,
): Promise<ResolverOutputWithMoved<ViewSquashed>> => {
try {
if (
input.created.length === 0
|| input.deleted.length === 0
|| renames.size === 0
) {
return {
created: input.created,
moved: [],
renamed: [],
deleted: input.deleted,
};
}
let createdViews = [...input.created];
let deletedViews = [...input.deleted];
const result: {
created: ViewSquashed[];
moved: { name: string; schemaFrom: string; schemaTo: string }[];
renamed: { from: ViewSquashed; to: ViewSquashed }[];
deleted: ViewSquashed[];
} = { created: [], renamed: [], deleted: [], moved: [] };
for (let rename of renames) {
const [from, to] = rename.split('->');
const idxFrom = deletedViews.findIndex((it) => {
return `${it.schema || 'public'}.${it.name}` === from;
});
if (idxFrom >= 0) {
const idxTo = createdViews.findIndex((it) => {
return `${it.schema || 'public'}.${it.name}` === to;
});
const viewFrom = deletedViews[idxFrom];
const viewTo = createdViews[idxFrom];
if (viewFrom.schema !== viewTo.schema) {
result.moved.push({
name: viewFrom.name,
schemaFrom: viewFrom.schema,
schemaTo: viewTo.schema,
});
}
if (viewFrom.name !== viewTo.name) {
result.renamed.push({
from: deletedViews[idxFrom],
to: createdViews[idxTo],
});
}
delete createdViews[idxTo];
delete deletedViews[idxFrom];
createdViews = createdViews.filter(Boolean);
deletedViews = deletedViews.filter(Boolean);
}
}
result.created = createdViews;
result.deleted = deletedViews;
return result;
} catch (e) {
console.error(e);
throw e;
}
};
export const testViewsResolverSqlite = (renames: Set<string>) =>
async (
input: ResolverInput<SqliteView>,
): Promise<ResolverOutputWithMoved<SqliteView>> => {
try {
if (
input.created.length === 0
|| input.deleted.length === 0
|| renames.size === 0
) {
return {
created: input.created,
moved: [],
renamed: [],
deleted: input.deleted,
};
}
let createdViews = [...input.created];
let deletedViews = [...input.deleted];
const result: {
created: SqliteView[];
moved: { name: string; schemaFrom: string; schemaTo: string }[];
renamed: { from: SqliteView; to: SqliteView }[];
deleted: SqliteView[];
} = { created: [], renamed: [], deleted: [], moved: [] };
for (let rename of renames) {
const [from, to] = rename.split('->');
const idxFrom = deletedViews.findIndex((it) => {
return it.name === from;
});
if (idxFrom >= 0) {
const idxTo = createdViews.findIndex((it) => {
return it.name === to;
});
const viewFrom = deletedViews[idxFrom];
const viewTo = createdViews[idxFrom];
if (viewFrom.name !== viewTo.name) {
result.renamed.push({
from: deletedViews[idxFrom],
to: createdViews[idxTo],
});
}
delete createdViews[idxTo];
delete deletedViews[idxFrom];
createdViews = createdViews.filter(Boolean);
deletedViews = deletedViews.filter(Boolean);
}
}
result.created = createdViews;
result.deleted = deletedViews;
return result;
} catch (e) {
console.error(e);
throw e;
}
};
export const diffTestSchemasPush = async (
client: PGlite,
left: PostgresSchema,
right: PostgresSchema,
renamesArr: string[],
cli: boolean = false,
schemas: string[] = ['public'],
casing?: CasingType | undefined,
entities?: Entities,
sqlStatementsToRun: {
before?: string[];
after?: string[];
runApply?: boolean;
} = {
before: [],
after: [],
runApply: true,
},
) => {
const shouldRunApply = sqlStatementsToRun.runApply === undefined
? true
: sqlStatementsToRun.runApply;
for (const st of sqlStatementsToRun.before ?? []) {
await client.query(st);
}
if (shouldRunApply) {
const res = await applyPgDiffs(left, casing);
for (const st of res.sqlStatements) {
await client.query(st);
}
}
for (const st of sqlStatementsToRun.after ?? []) {
await client.query(st);
}
const materializedViewsForRefresh = Object.values(left).filter((it) =>
isPgMaterializedView(it)
) as PgMaterializedView[];
// refresh all mat views
for (const view of materializedViewsForRefresh) {
const viewConf = getMaterializedViewConfig(view);
if (viewConf.isExisting) continue;
await client.exec(
`REFRESH MATERIALIZED VIEW "${viewConf.schema ?? 'public'}"."${viewConf.name}"${
viewConf.withNoData ? ' WITH NO DATA;' : ';'
}`,
);
}
// do introspect into PgSchemaInternal
const introspectedSchema = await fromDatabase(
{
query: async (query: string, values?: any[] | undefined) => {
const res = await client.query(query, values);
return res.rows as any[];
},
},
undefined,
schemas,
entities,
);
const leftTables = Object.values(right).filter((it) => is(it, PgTable)) as PgTable[];
const leftSchemas = Object.values(right).filter((it) => is(it, PgSchema)) as PgSchema[];
const leftEnums = Object.values(right).filter((it) => isPgEnum(it)) as PgEnum<any>[];
const leftSequences = Object.values(right).filter((it) => isPgSequence(it)) as PgSequence[];
const leftRoles = Object.values(right).filter((it) => is(it, PgRole)) as PgRole[];
const leftPolicies = Object.values(right).filter((it) => is(it, PgPolicy)) as PgPolicy[];
const leftViews = Object.values(right).filter((it) => isPgView(it)) as PgView[];
const leftMaterializedViews = Object.values(right).filter((it) => isPgMaterializedView(it)) as PgMaterializedView[];
const serialized2 = generatePgSnapshot(
leftTables,
leftEnums,
leftSchemas,
leftSequences,
leftRoles,
leftPolicies,
leftViews,
leftMaterializedViews,
casing,
);
const { version: v1, dialect: d1, ...rest1 } = introspectedSchema;
const { version: v2, dialect: d2, ...rest2 } = serialized2;
const sch1 = {
version: '7',
dialect: 'postgresql',
id: '0',
prevId: '0',
...rest1,
} as const;
const sch2 = {
version: '7',
dialect: 'postgresql',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn1 = squashPgScheme(sch1, 'push');
const sn2 = squashPgScheme(sch2, 'push');
const validatedPrev = pgSchema.parse(sch1);
const validatedCur = pgSchema.parse(sch2);
const renames = new Set(renamesArr);
if (!cli) {
const { sqlStatements, statements } = await applyPgSnapshotsDiff(
sn1,
sn2,
testSchemasResolver(renames),
testEnumsResolver(renames),
testSequencesResolver(renames),
testPolicyResolver(renames),
testIndPolicyResolver(renames),
testRolesResolver(renames),
testTablesResolver(renames),
testColumnsResolver(renames),
testViewsResolver(renames),
validatedPrev,
validatedCur,
'push',
);
const {
shouldAskForApprove,
statementsToExecute,
columnsToRemove,
tablesToRemove,
tablesToTruncate,
infoToPrint,
schemasToRemove,
matViewsToRemove,
} = await pgSuggestions(
{
query: async <T>(sql: string, params: any[] = []) => {
return (await client.query(sql, params)).rows as T[];
},
},
statements,
);
return {
sqlStatements: statementsToExecute,
statements,
shouldAskForApprove,
columnsToRemove,
tablesToRemove,
tablesToTruncate,
infoToPrint,
schemasToRemove,
matViewsToRemove,
};
} else {
const { sqlStatements, statements } = await applyPgSnapshotsDiff(
sn1,
sn2,
schemasResolver,
enumsResolver,
sequencesResolver,
policyResolver,
indPolicyResolver,
roleResolver,
tablesResolver,
columnsResolver,
viewsResolver,
validatedPrev,
validatedCur,
'push',
);
return { sqlStatements, statements };
}
};
export const applyPgDiffs = async (
sn: PostgresSchema,
casing: CasingType | undefined,
) => {
const dryRun = {
version: '7',
dialect: 'postgresql',
id: '0',
prevId: '0',
tables: {},
enums: {},
views: {},
schemas: {},
sequences: {},
policies: {},
roles: {},
_meta: {
schemas: {},
tables: {},
columns: {},
},
} as const;
const tables = Object.values(sn).filter((it) => is(it, PgTable)) as PgTable[];
const schemas = Object.values(sn).filter((it) => is(it, PgSchema)) as PgSchema[];
const enums = Object.values(sn).filter((it) => isPgEnum(it)) as PgEnum<any>[];
const sequences = Object.values(sn).filter((it) => isPgSequence(it)) as PgSequence[];
const roles = Object.values(sn).filter((it) => is(it, PgRole)) as PgRole[];
const views = Object.values(sn).filter((it) => isPgView(it)) as PgView[];
const policies = Object.values(sn).filter((it) => is(it, PgPolicy)) as PgPolicy[];
const materializedViews = Object.values(sn).filter((it) => isPgMaterializedView(it)) as PgMaterializedView[];
const serialized1 = generatePgSnapshot(
tables,
enums,
schemas,
sequences,
roles,
policies,
views,
materializedViews,
casing,
);
const { version: v1, dialect: d1, ...rest1 } = serialized1;
const sch1 = {
version: '7',
dialect: 'postgresql',
id: '0',
prevId: '0',
...rest1,
} as const;
const sn1 = squashPgScheme(sch1);
const validatedPrev = pgSchema.parse(dryRun);
const validatedCur = pgSchema.parse(sch1);
const { sqlStatements, statements } = await applyPgSnapshotsDiff(
dryRun,
sn1,
testSchemasResolver(new Set()),
testEnumsResolver(new Set()),
testSequencesResolver(new Set()),
testPolicyResolver(new Set()),
testIndPolicyResolver(new Set()),
testRolesResolver(new Set()),
testTablesResolver(new Set()),
testColumnsResolver(new Set()),
testViewsResolver(new Set()),
validatedPrev,
validatedCur,
);
return { sqlStatements, statements };
};
export const diffTestSchemas = async (
left: PostgresSchema,
right: PostgresSchema,
renamesArr: string[],
cli: boolean = false,
casing?: CasingType | undefined,
) => {
const leftTables = Object.values(left).filter((it) => is(it, PgTable)) as PgTable[];
const rightTables = Object.values(right).filter((it) => is(it, PgTable)) as PgTable[];
const leftSchemas = Object.values(left).filter((it) => is(it, PgSchema)) as PgSchema[];
const rightSchemas = Object.values(right).filter((it) => is(it, PgSchema)) as PgSchema[];
const leftEnums = Object.values(left).filter((it) => isPgEnum(it)) as PgEnum<any>[];
const rightEnums = Object.values(right).filter((it) => isPgEnum(it)) as PgEnum<any>[];
const leftSequences = Object.values(left).filter((it) => isPgSequence(it)) as PgSequence[];
const rightSequences = Object.values(right).filter((it) => isPgSequence(it)) as PgSequence[];
const leftRoles = Object.values(left).filter((it) => is(it, PgRole)) as PgRole[];
const rightRoles = Object.values(right).filter((it) => is(it, PgRole)) as PgRole[];
const leftPolicies = Object.values(left).filter((it) => is(it, PgPolicy)) as PgPolicy[];
const rightPolicies = Object.values(right).filter((it) => is(it, PgPolicy)) as PgPolicy[];
const leftViews = Object.values(left).filter((it) => isPgView(it)) as PgView[];
const rightViews = Object.values(right).filter((it) => isPgView(it)) as PgView[];
const leftMaterializedViews = Object.values(left).filter((it) => isPgMaterializedView(it)) as PgMaterializedView[];
const rightMaterializedViews = Object.values(right).filter((it) => isPgMaterializedView(it)) as PgMaterializedView[];
const serialized1 = generatePgSnapshot(
leftTables,
leftEnums,
leftSchemas,
leftSequences,
leftRoles,
leftPolicies,
leftViews,
leftMaterializedViews,
casing,
);
const serialized2 = generatePgSnapshot(
rightTables,
rightEnums,
rightSchemas,
rightSequences,
rightRoles,
rightPolicies,
rightViews,
rightMaterializedViews,
casing,
);
const { version: v1, dialect: d1, ...rest1 } = serialized1;
const { version: v2, dialect: d2, ...rest2 } = serialized2;
const sch1 = {
version: '7',
dialect: 'postgresql',
id: '0',
prevId: '0',
...rest1,
} as const;
const sch2 = {
version: '7',
dialect: 'postgresql',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn1 = squashPgScheme(sch1);
const sn2 = squashPgScheme(sch2);
const validatedPrev = pgSchema.parse(sch1);
const validatedCur = pgSchema.parse(sch2);
const renames = new Set(renamesArr);
if (!cli) {
const { sqlStatements, statements } = await applyPgSnapshotsDiff(
sn1,
sn2,
testSchemasResolver(renames),
testEnumsResolver(renames),
testSequencesResolver(renames),
testPolicyResolver(renames),
testIndPolicyResolver(renames),
testRolesResolver(renames),
testTablesResolver(renames),
testColumnsResolver(renames),
testViewsResolver(renames),
validatedPrev,
validatedCur,
);
return { sqlStatements, statements };
} else {
const { sqlStatements, statements } = await applyPgSnapshotsDiff(
sn1,
sn2,
schemasResolver,
enumsResolver,
sequencesResolver,
policyResolver,
indPolicyResolver,
roleResolver,
tablesResolver,
columnsResolver,
viewsResolver,
validatedPrev,
validatedCur,
);
return { sqlStatements, statements };
}
};
export const diffTestSchemasPushMysql = async (
client: Connection,
left: MysqlSchema,
right: MysqlSchema,
renamesArr: string[],
schema: string,
cli: boolean = false,
casing?: CasingType | undefined,
) => {
const { sqlStatements } = await applyMySqlDiffs(left, casing);
for (const st of sqlStatements) {
await client.query(st);
}
// do introspect into PgSchemaInternal
const introspectedSchema = await fromMySqlDatabase(
{
query: async (sql: string, params?: any[]) => {
const res = await client.execute(sql, params);
return res[0] as any;
},
},
schema,
);
const leftTables = Object.values(right).filter((it) => is(it, MySqlTable)) as MySqlTable[];
const leftViews = Object.values(right).filter((it) => is(it, MySqlView)) as MySqlView[];
const serialized2 = generateMySqlSnapshot(leftTables, leftViews, casing);
const { version: v1, dialect: d1, ...rest1 } = introspectedSchema;
const { version: v2, dialect: d2, ...rest2 } = serialized2;
const sch1 = {
version: '5',
dialect: 'mysql',
id: '0',
prevId: '0',
...rest1,
} as const;
const sch2 = {
version: '5',
dialect: 'mysql',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn1 = squashMysqlScheme(sch1);
const sn2 = squashMysqlScheme(sch2);
const validatedPrev = mysqlSchema.parse(sch1);
const validatedCur = mysqlSchema.parse(sch2);
const renames = new Set(renamesArr);
if (!cli) {
const { sqlStatements, statements } = await applyMysqlSnapshotsDiff(
sn1,
sn2,
testTablesResolver(renames),
testColumnsResolver(renames),
testViewsResolverMySql(renames),
validatedPrev,
validatedCur,
'push',
);
return { sqlStatements, statements };
} else {
const { sqlStatements, statements } = await applyMysqlSnapshotsDiff(
sn1,
sn2,
tablesResolver,
columnsResolver,
mySqlViewsResolver,
validatedPrev,
validatedCur,
'push',
);
return { sqlStatements, statements };
}
};
export const applyMySqlDiffs = async (
sn: MysqlSchema,
casing: CasingType | undefined,
) => {
const dryRun = {
version: '5',
dialect: 'mysql',
id: '0',
prevId: '0',
views: {},
tables: {},
enums: {},
schemas: {},
_meta: {
schemas: {},
tables: {},
columns: {},
},
} as const;
const tables = Object.values(sn).filter((it) => is(it, MySqlTable)) as MySqlTable[];
const views = Object.values(sn).filter((it) => is(it, MySqlView)) as MySqlView[];
const serialized1 = generateMySqlSnapshot(tables, views, casing);
const { version: v1, dialect: d1, ...rest1 } = serialized1;
const sch1 = {
version: '5',
dialect: 'mysql',
id: '0',
prevId: '0',
...rest1,
} as const;
const sn1 = squashMysqlScheme(sch1);
const validatedPrev = mysqlSchema.parse(dryRun);
const validatedCur = mysqlSchema.parse(sch1);
const { sqlStatements, statements } = await applyMysqlSnapshotsDiff(
dryRun,
sn1,
testTablesResolver(new Set()),
testColumnsResolver(new Set()),
testViewsResolverMySql(new Set()),
validatedPrev,
validatedCur,
);
return { sqlStatements, statements };
};
export const diffTestSchemasMysql = async (
left: MysqlSchema,
right: MysqlSchema,
renamesArr: string[],
cli: boolean = false,
casing?: CasingType | undefined,
) => {
const leftTables = Object.values(left).filter((it) => is(it, MySqlTable)) as MySqlTable[];
const leftViews = Object.values(left).filter((it) => is(it, MySqlView)) as MySqlView[];
const rightTables = Object.values(right).filter((it) => is(it, MySqlTable)) as MySqlTable[];
const rightViews = Object.values(right).filter((it) => is(it, MySqlView)) as MySqlView[];
const serialized1 = generateMySqlSnapshot(leftTables, leftViews, casing);
const serialized2 = generateMySqlSnapshot(rightTables, rightViews, casing);
const { version: v1, dialect: d1, ...rest1 } = serialized1;
const { version: v2, dialect: d2, ...rest2 } = serialized2;
const sch1 = {
version: '5',
dialect: 'mysql',
id: '0',
prevId: '0',
...rest1,
} as const;
const sch2 = {
version: '5',
dialect: 'mysql',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn1 = squashMysqlScheme(sch1);
const sn2 = squashMysqlScheme(sch2);
const validatedPrev = mysqlSchema.parse(sch1);
const validatedCur = mysqlSchema.parse(sch2);
const renames = new Set(renamesArr);
if (!cli) {
const { sqlStatements, statements } = await applyMysqlSnapshotsDiff(
sn1,
sn2,
testTablesResolver(renames),
testColumnsResolver(renames),
testViewsResolverMySql(renames),
validatedPrev,
validatedCur,
);
return { sqlStatements, statements };
}
const { sqlStatements, statements } = await applyMysqlSnapshotsDiff(
sn1,
sn2,
tablesResolver,
columnsResolver,
mySqlViewsResolver,
validatedPrev,
validatedCur,
);
return { sqlStatements, statements };
};
export const diffTestSchemasSingleStore = async (
left: SinglestoreSchema,
right: SinglestoreSchema,
renamesArr: string[],
cli: boolean = false,
casing?: CasingType | undefined,
) => {
const leftTables = Object.values(left).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[];
/* const leftViews = Object.values(left).filter((it) => is(it, SingleStoreView)) as SingleStoreView[]; */
const rightTables = Object.values(right).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[];
/* const rightViews = Object.values(right).filter((it) => is(it, SingleStoreView)) as SingleStoreView[]; */
const serialized1 = generateSingleStoreSnapshot(
leftTables,
/* leftViews, */
casing,
);
const serialized2 = generateSingleStoreSnapshot(
rightTables,
/* rightViews, */
casing,
);
const { version: v1, dialect: d1, ...rest1 } = serialized1;
const { version: v2, dialect: d2, ...rest2 } = serialized2;
const sch1 = {
version: '1',
dialect: 'singlestore',
id: '0',
prevId: '0',
...rest1,
} as const;
const sch2 = {
version: '1',
dialect: 'singlestore',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn1 = squashSingleStoreScheme(sch1);
const sn2 = squashSingleStoreScheme(sch2);
const validatedPrev = singlestoreSchema.parse(sch1);
const validatedCur = singlestoreSchema.parse(sch2);
const renames = new Set(renamesArr);
if (!cli) {
const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff(
sn1,
sn2,
testTablesResolver(renames),
testColumnsResolver(renames),
/* testViewsResolverSingleStore(renames), */
validatedPrev,
validatedCur,
);
return { sqlStatements, statements };
}
const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff(
sn1,
sn2,
tablesResolver,
columnsResolver,
/* singleStoreViewsResolver, */
validatedPrev,
validatedCur,
);
return { sqlStatements, statements };
};
export const diffTestSchemasPushSingleStore = async (
client: Connection,
left: SinglestoreSchema,
right: SinglestoreSchema,
renamesArr: string[],
schema: string,
cli: boolean = false,
casing?: CasingType | undefined,
sqlStatementsToRun: {
before?: string[];
after?: string[];
runApply?: boolean;
} = {
before: [],
after: [],
runApply: true,
},
) => {
const shouldRunApply = sqlStatementsToRun.runApply === undefined
? true
: sqlStatementsToRun.runApply;
for (const st of sqlStatementsToRun.before ?? []) {
await client.query(st);
}
if (shouldRunApply) {
const res = await applySingleStoreDiffs(left, casing);
for (const st of res.sqlStatements) {
await client.query(st);
}
}
for (const st of sqlStatementsToRun.after ?? []) {
await client.query(st);
}
// do introspect into PgSchemaInternal
const introspectedSchema = await fromSingleStoreDatabase(
{
query: async (sql: string, params?: any[]) => {
const res = await client.execute(sql, params);
return res[0] as any;
},
},
schema,
);
const leftTables = Object.values(right).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[];
/* const leftViews = Object.values(right).filter((it) => is(it, SingleStoreView)) as SingleStoreView[]; */
const serialized2 = generateSingleStoreSnapshot(
leftTables,
/* leftViews, */
casing,
);
const { version: v1, dialect: d1, ...rest1 } = introspectedSchema;
const { version: v2, dialect: d2, ...rest2 } = serialized2;
const sch1 = {
version: '1',
dialect: 'singlestore',
id: '0',
prevId: '0',
...rest1,
} as const;
const sch2 = {
version: '1',
dialect: 'singlestore',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn1 = squashSingleStoreScheme(sch1);
const sn2 = squashSingleStoreScheme(sch2);
const validatedPrev = singlestoreSchema.parse(sch1);
const validatedCur = singlestoreSchema.parse(sch2);
const renames = new Set(renamesArr);
if (!cli) {
const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff(
sn1,
sn2,
testTablesResolver(renames),
testColumnsResolver(renames),
/* testViewsResolverSingleStore(renames), */
validatedPrev,
validatedCur,
'push',
);
const {
statementsToExecute,
columnsToRemove,
infoToPrint,
shouldAskForApprove,
tablesToRemove,
tablesToTruncate,
} = await singleStoreLogSuggestionsAndReturn(
{
query: async <T>(sql: string, params?: any[]) => {
const res = await client.execute(sql, params);
return res[0] as T[];
},
},
statements,
sn1,
sn2,
);
return {
sqlStatements: statementsToExecute,
statements,
columnsToRemove,
infoToPrint,
shouldAskForApprove,
tablesToRemove,
tablesToTruncate,
};
} else {
const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff(
sn1,
sn2,
tablesResolver,
columnsResolver,
/* singleStoreViewsResolver, */
validatedPrev,
validatedCur,
'push',
);
return { sqlStatements, statements };
}
};
export const applySingleStoreDiffs = async (
sn: SinglestoreSchema,
casing: CasingType | undefined,
) => {
const dryRun = {
version: '1',
dialect: 'singlestore',
id: '0',
prevId: '0',
tables: {},
views: {},
enums: {},
schemas: {},
_meta: {
schemas: {},
tables: {},
columns: {},
},
} as const;
const tables = Object.values(sn).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[];
/* const views = Object.values(sn).filter((it) => is(it, SingleStoreView)) as SingleStoreView[]; */
const serialized1 = generateSingleStoreSnapshot(tables, /* views, */ casing);
const { version: v1, dialect: d1, ...rest1 } = serialized1;
const sch1 = {
version: '1',
dialect: 'singlestore',
id: '0',
prevId: '0',
...rest1,
} as const;
const sn1 = squashSingleStoreScheme(sch1);
const validatedPrev = singlestoreSchema.parse(dryRun);
const validatedCur = singlestoreSchema.parse(sch1);
const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff(
dryRun,
sn1,
testTablesResolver(new Set()),
testColumnsResolver(new Set()),
/* testViewsResolverSingleStore(new Set()), */
validatedPrev,
validatedCur,
);
return { sqlStatements, statements };
};
export const diffTestSchemasPushSqlite = async (
client: Database,
left: SqliteSchema,
right: SqliteSchema,
renamesArr: string[],
cli: boolean = false,
seedStatements: string[] = [],
casing?: CasingType | undefined,
) => {
const { sqlStatements } = await applySqliteDiffs(left, 'push');
for (const st of sqlStatements) {
client.exec(st);
}
for (const st of seedStatements) {
client.exec(st);
}
// do introspect into PgSchemaInternal
const introspectedSchema = await fromSqliteDatabase(
{
query: async <T>(sql: string, params: any[] = []) => {
return client.prepare(sql).bind(params).all() as T[];
},
run: async (query: string) => {
client.prepare(query).run();
},
},
undefined,
);
const rightTables = Object.values(right).filter((it) => is(it, SQLiteTable)) as SQLiteTable[];
const rightViews = Object.values(right).filter((it) => is(it, SQLiteView)) as SQLiteView[];
const serialized2 = generateSqliteSnapshot(rightTables, rightViews, casing);
const { version: v1, dialect: d1, ...rest1 } = introspectedSchema;
const { version: v2, dialect: d2, ...rest2 } = serialized2;
const sch1 = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...rest1,
} as const;
const sch2 = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn1 = squashSqliteScheme(sch1, 'push');
const sn2 = squashSqliteScheme(sch2, 'push');
const renames = new Set(renamesArr);
if (!cli) {
const { sqlStatements, statements, _meta } = await applySqliteSnapshotsDiff(
sn1,
sn2,
testTablesResolver(renames),
testColumnsResolver(renames),
testViewsResolverSqlite(renames),
sch1,
sch2,
'push',
);
const {
statementsToExecute,
columnsToRemove,
infoToPrint,
schemasToRemove,
shouldAskForApprove,
tablesToRemove,
tablesToTruncate,
} = await logSuggestionsAndReturn(
{
query: async <T>(sql: string, params: any[] = []) => {
return client.prepare(sql).bind(params).all() as T[];
},
run: async (query: string) => {
client.prepare(query).run();
},
},
statements,
sn1,
sn2,
_meta!,
);
return {
sqlStatements: statementsToExecute,
statements,
columnsToRemove,
infoToPrint,
schemasToRemove,
shouldAskForApprove,
tablesToRemove,
tablesToTruncate,
};
} else {
const { sqlStatements, statements } = await applySqliteSnapshotsDiff(
sn1,
sn2,
tablesResolver,
columnsResolver,
sqliteViewsResolver,
sch1,
sch2,
'push',
);
return { sqlStatements, statements };
}
};
export async function diffTestSchemasPushLibSQL(
client: Client,
left: SqliteSchema,
right: SqliteSchema,
renamesArr: string[],
cli: boolean = false,
seedStatements: string[] = [],
casing?: CasingType | undefined,
) {
const { sqlStatements } = await applyLibSQLDiffs(left, 'push');
for (const st of sqlStatements) {
await client.execute(st);
}
for (const st of seedStatements) {
await client.execute(st);
}
const introspectedSchema = await fromSqliteDatabase(
{
query: async <T>(sql: string, params?: any[]) => {
const res = await client.execute({ sql, args: params || [] });
return res.rows as T[];
},
run: async (query: string) => {
await client.execute(query);
},
},
undefined,
);
const leftTables = Object.values(right).filter((it) => is(it, SQLiteTable)) as SQLiteTable[];
const leftViews = Object.values(right).filter((it) => is(it, SQLiteView)) as SQLiteView[];
const serialized2 = generateSqliteSnapshot(leftTables, leftViews, casing);
const { version: v1, dialect: d1, ...rest1 } = introspectedSchema;
const { version: v2, dialect: d2, ...rest2 } = serialized2;
const sch1 = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...rest1,
} as const;
const sch2 = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn1 = squashSqliteScheme(sch1, 'push');
const sn2 = squashSqliteScheme(sch2, 'push');
const renames = new Set(renamesArr);
if (!cli) {
const { sqlStatements, statements, _meta } = await applyLibSQLSnapshotsDiff(
sn1,
sn2,
testTablesResolver(renames),
testColumnsResolver(renames),
testViewsResolverSqlite(renames),
sch1,
sch2,
'push',
);
const {
statementsToExecute,
columnsToRemove,
infoToPrint,
shouldAskForApprove,
tablesToRemove,
tablesToTruncate,
} = await libSqlLogSuggestionsAndReturn(
{
query: async <T>(sql: string, params?: any[]) => {
const res = await client.execute({ sql, args: params || [] });
return res.rows as T[];
},
run: async (query: string) => {
await client.execute(query);
},
},
statements,
sn1,
sn2,
_meta!,
);
return {
sqlStatements: statementsToExecute,
statements,
columnsToRemove,
infoToPrint,
shouldAskForApprove,
tablesToRemove,
tablesToTruncate,
};
} else {
const { sqlStatements, statements } = await applyLibSQLSnapshotsDiff(
sn1,
sn2,
tablesResolver,
columnsResolver,
sqliteViewsResolver,
sch1,
sch2,
'push',
);
return { sqlStatements, statements };
}
}
export const applySqliteDiffs = async (
sn: SqliteSchema,
action?: 'push' | undefined,
casing?: CasingType | undefined,
) => {
const dryRun = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
tables: {},
enums: {},
views: {},
schemas: {},
_meta: {
schemas: {},
tables: {},
columns: {},
},
} as const;
const tables = Object.values(sn).filter((it) => is(it, SQLiteTable)) as SQLiteTable[];
const views = Object.values(sn).filter((it) => is(it, SQLiteView)) as SQLiteView[];
const serialized1 = generateSqliteSnapshot(tables, views, casing);
const { version: v1, dialect: d1, ...rest1 } = serialized1;
const sch1 = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...rest1,
} as const;
const sn1 = squashSqliteScheme(sch1, action);
const { sqlStatements, statements } = await applySqliteSnapshotsDiff(
dryRun,
sn1,
testTablesResolver(new Set()),
testColumnsResolver(new Set()),
testViewsResolverSqlite(new Set()),
dryRun,
sch1,
action,
);
return { sqlStatements, statements };
};
export const applyLibSQLDiffs = async (
sn: SqliteSchema,
action?: 'push' | undefined,
casing?: CasingType | undefined,
) => {
const dryRun = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
tables: {},
views: {},
enums: {},
schemas: {},
_meta: {
schemas: {},
tables: {},
columns: {},
},
} as const;
const tables = Object.values(sn).filter((it) => is(it, SQLiteTable)) as SQLiteTable[];
const views = Object.values(sn).filter((it) => is(it, SQLiteView)) as SQLiteView[];
const serialized1 = generateSqliteSnapshot(tables, views, casing);
const { version: v1, dialect: d1, ...rest1 } = serialized1;
const sch1 = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...rest1,
} as const;
const sn1 = squashSqliteScheme(sch1, action);
const { sqlStatements, statements } = await applyLibSQLSnapshotsDiff(
dryRun,
sn1,
testTablesResolver(new Set()),
testColumnsResolver(new Set()),
testViewsResolverSqlite(new Set()),
dryRun,
sch1,
action,
);
return { sqlStatements, statements };
};
export const diffTestSchemasSqlite = async (
left: SqliteSchema,
right: SqliteSchema,
renamesArr: string[],
cli: boolean = false,
casing?: CasingType | undefined,
) => {
const leftTables = Object.values(left).filter((it) => is(it, SQLiteTable)) as SQLiteTable[];
const leftViews = Object.values(left).filter((it) => is(it, SQLiteView)) as SQLiteView[];
const rightTables = Object.values(right).filter((it) => is(it, SQLiteTable)) as SQLiteTable[];
const rightViews = Object.values(right).filter((it) => is(it, SQLiteView)) as SQLiteView[];
const serialized1 = generateSqliteSnapshot(leftTables, leftViews, casing);
const serialized2 = generateSqliteSnapshot(rightTables, rightViews, casing);
const { version: v1, dialect: d1, ...rest1 } = serialized1;
const { version: v2, dialect: d2, ...rest2 } = serialized2;
const sch1 = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...rest1,
} as const;
const sch2 = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn1 = squashSqliteScheme(sch1);
const sn2 = squashSqliteScheme(sch2);
const renames = new Set(renamesArr);
if (!cli) {
const { sqlStatements, statements } = await applySqliteSnapshotsDiff(
sn1,
sn2,
testTablesResolver(renames),
testColumnsResolver(renames),
testViewsResolverSqlite(renames),
sch1,
sch2,
);
return { sqlStatements, statements };
}
const { sqlStatements, statements } = await applySqliteSnapshotsDiff(
sn1,
sn2,
tablesResolver,
columnsResolver,
sqliteViewsResolver,
sch1,
sch2,
);
return { sqlStatements, statements };
};
export const diffTestSchemasLibSQL = async (
left: SqliteSchema,
right: SqliteSchema,
renamesArr: string[],
cli: boolean = false,
casing?: CasingType | undefined,
) => {
const leftTables = Object.values(left).filter((it) => is(it, SQLiteTable)) as SQLiteTable[];
const leftViews = Object.values(left).filter((it) => is(it, SQLiteView)) as SQLiteView[];
const rightTables = Object.values(right).filter((it) => is(it, SQLiteTable)) as SQLiteTable[];
const rightViews = Object.values(right).filter((it) => is(it, SQLiteView)) as SQLiteView[];
const serialized1 = generateSqliteSnapshot(leftTables, leftViews, casing);
const serialized2 = generateSqliteSnapshot(rightTables, rightViews, casing);
const { version: v1, dialect: d1, ...rest1 } = serialized1;
const { version: v2, dialect: d2, ...rest2 } = serialized2;
const sch1 = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...rest1,
} as const;
const sch2 = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn1 = squashSqliteScheme(sch1);
const sn2 = squashSqliteScheme(sch2);
const renames = new Set(renamesArr);
if (!cli) {
const { sqlStatements, statements } = await applyLibSQLSnapshotsDiff(
sn1,
sn2,
testTablesResolver(renames),
testColumnsResolver(renames),
testViewsResolverSqlite(renames),
sch1,
sch2,
);
return { sqlStatements, statements };
}
const { sqlStatements, statements } = await applyLibSQLSnapshotsDiff(
sn1,
sn2,
tablesResolver,
columnsResolver,
sqliteViewsResolver,
sch1,
sch2,
);
return { sqlStatements, statements };
};
// --- Introspect to file helpers ---
export const introspectPgToFile = async (
client: PGlite,
initSchema: PostgresSchema,
testName: string,
schemas: string[] = ['public'],
entities?: Entities,
casing?: CasingType | undefined,
) => {
// put in db
const { sqlStatements } = await applyPgDiffs(initSchema, casing);
for (const st of sqlStatements) {
await client.query(st);
}
// introspect to schema
const introspectedSchema = await fromDatabase(
{
query: async (query: string, values?: any[] | undefined) => {
const res = await client.query(query, values);
return res.rows as any[];
},
},
undefined,
schemas,
entities,
);
const { version: initV, dialect: initD, ...initRest } = introspectedSchema;
const initSch = {
version: '7',
dialect: 'postgresql',
id: '0',
prevId: '0',
...initRest,
} as const;
const initSn = squashPgScheme(initSch);
const validatedCur = pgSchema.parse(initSch);
// write to ts file
const file = schemaToTypeScript(introspectedSchema, 'camel');
fs.writeFileSync(`tests/introspect/postgres/${testName}.ts`, file.file);
// generate snapshot from ts file
const response = await prepareFromPgImports([
`tests/introspect/postgres/${testName}.ts`,
]);
const afterFileImports = generatePgSnapshot(
response.tables,
response.enums,
response.schemas,
response.sequences,
response.roles,
response.policies,
response.views,
response.matViews,
casing,
);
const { version: v2, dialect: d2, ...rest2 } = afterFileImports;
const sch2 = {
version: '7',
dialect: 'postgresql',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn2AfterIm = squashPgScheme(sch2);
const validatedCurAfterImport = pgSchema.parse(sch2);
const {
sqlStatements: afterFileSqlStatements,
statements: afterFileStatements,
} = await applyPgSnapshotsDiff(
initSn,
sn2AfterIm,
testSchemasResolver(new Set()),
testEnumsResolver(new Set()),
testSequencesResolver(new Set()),
testPolicyResolver(new Set()),
testIndPolicyResolver(new Set()),
testRolesResolver(new Set()),
testTablesResolver(new Set()),
testColumnsResolver(new Set()),
testViewsResolver(new Set()),
validatedCur,
validatedCurAfterImport,
);
fs.rmSync(`tests/introspect/postgres/${testName}.ts`);
return {
sqlStatements: afterFileSqlStatements,
statements: afterFileStatements,
};
};
export const introspectGelToFile = async (
client: GelClient,
testName: string,
schemas: string[] = ['public'],
entities?: Entities,
casing?: CasingType | undefined,
) => {
// introspect to schema
const introspectedSchema = await fromGelDatabase(
{
query: async (query: string, values?: any[] | undefined) => {
const res = await client.querySQL(query, values);
return res as any[];
},
},
undefined,
schemas,
entities,
);
// write to ts file
const file = schemaToTypeScriptGel(introspectedSchema, 'camel');
const path = `tests/introspect/gel/${testName}.ts`;
fs.writeFileSync(path, file.file);
return path;
};
export const introspectMySQLToFile = async (
client: Connection,
initSchema: MysqlSchema,
testName: string,
schema: string,
casing?: CasingType | undefined,
) => {
// put in db
const { sqlStatements } = await applyMySqlDiffs(initSchema, casing);
for (const st of sqlStatements) {
await client.query(st);
}
// introspect to schema
const introspectedSchema = await fromMySqlDatabase(
{
query: async (sql: string, params?: any[] | undefined) => {
const res = await client.execute(sql, params);
return res[0] as any;
},
},
schema,
);
const { version: initV, dialect: initD, ...initRest } = introspectedSchema;
const initSch = {
version: '5',
dialect: 'mysql',
id: '0',
prevId: '0',
...initRest,
} as const;
const initSn = squashMysqlScheme(initSch);
const validatedCur = mysqlSchema.parse(initSch);
const file = schemaToTypeScriptMySQL(introspectedSchema, 'camel');
fs.writeFileSync(`tests/introspect/mysql/${testName}.ts`, file.file);
const response = await prepareFromMySqlImports([
`tests/introspect/mysql/${testName}.ts`,
]);
const afterFileImports = generateMySqlSnapshot(
response.tables,
response.views,
casing,
);
const { version: v2, dialect: d2, ...rest2 } = afterFileImports;
const sch2 = {
version: '5',
dialect: 'mysql',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn2AfterIm = squashMysqlScheme(sch2);
const validatedCurAfterImport = mysqlSchema.parse(sch2);
const {
sqlStatements: afterFileSqlStatements,
statements: afterFileStatements,
} = await applyMysqlSnapshotsDiff(
sn2AfterIm,
initSn,
testTablesResolver(new Set()),
testColumnsResolver(new Set()),
testViewsResolverMySql(new Set()),
validatedCurAfterImport,
validatedCur,
);
fs.rmSync(`tests/introspect/mysql/${testName}.ts`);
return {
sqlStatements: afterFileSqlStatements,
statements: afterFileStatements,
};
};
export const introspectSingleStoreToFile = async (
client: Connection,
initSchema: SinglestoreSchema,
testName: string,
schema: string,
casing?: CasingType | undefined,
) => {
// put in db
const { sqlStatements } = await applySingleStoreDiffs(initSchema, casing);
for (const st of sqlStatements) {
await client.query(st);
}
// introspect to schema
const introspectedSchema = await fromSingleStoreDatabase(
{
query: async (sql: string, params?: any[] | undefined) => {
const res = await client.execute(sql, params);
return res[0] as any;
},
},
schema,
);
const file = schemaToTypeScriptSingleStore(introspectedSchema, 'camel');
fs.writeFileSync(`tests/introspect/singlestore/${testName}.ts`, file.file);
const response = await prepareFromSingleStoreImports([
`tests/introspect/singlestore/${testName}.ts`,
]);
const afterFileImports = generateSingleStoreSnapshot(
response.tables,
/* response.views, */
casing,
);
const { version: v2, dialect: d2, ...rest2 } = afterFileImports;
const sch2 = {
version: '1',
dialect: 'singlestore',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn2AfterIm = squashSingleStoreScheme(sch2);
const validatedCurAfterImport = singlestoreSchema.parse(sch2);
const leftTables = Object.values(initSchema).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[];
const initSnapshot = generateSingleStoreSnapshot(
leftTables,
/* response.views, */
casing,
);
const { version: initV, dialect: initD, ...initRest } = initSnapshot;
const initSch = {
version: '1',
dialect: 'singlestore',
id: '0',
prevId: '0',
...initRest,
} as const;
const initSn = squashSingleStoreScheme(initSch);
const validatedCur = singlestoreSchema.parse(initSch);
const {
sqlStatements: afterFileSqlStatements,
statements: afterFileStatements,
} = await applySingleStoreSnapshotsDiff(
sn2AfterIm,
initSn,
testTablesResolver(new Set()),
testColumnsResolver(new Set()),
/* testViewsResolverSingleStore(new Set()), */
validatedCurAfterImport,
validatedCur,
);
fs.rmSync(`tests/introspect/singlestore/${testName}.ts`);
return {
sqlStatements: afterFileSqlStatements,
statements: afterFileStatements,
};
};
export const introspectSQLiteToFile = async (
client: Database,
initSchema: SqliteSchema,
testName: string,
casing?: CasingType | undefined,
) => {
// put in db
const { sqlStatements } = await applySqliteDiffs(initSchema);
for (const st of sqlStatements) {
client.exec(st);
}
// introspect to schema
const introspectedSchema = await fromSqliteDatabase(
{
query: async <T>(sql: string, params: any[] = []) => {
return client.prepare(sql).bind(params).all() as T[];
},
run: async (query: string) => {
client.prepare(query).run();
},
},
undefined,
);
const { version: initV, dialect: initD, ...initRest } = introspectedSchema;
const initSch = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...initRest,
} as const;
const initSn = squashSqliteScheme(initSch);
const validatedCur = sqliteSchema.parse(initSch);
const file = schemaToTypeScriptSQLite(introspectedSchema, 'camel');
fs.writeFileSync(`tests/introspect/sqlite/${testName}.ts`, file.file);
const response = await prepareFromSqliteImports([
`tests/introspect/sqlite/${testName}.ts`,
]);
const afterFileImports = generateSqliteSnapshot(
response.tables,
response.views,
casing,
);
const { version: v2, dialect: d2, ...rest2 } = afterFileImports;
const sch2 = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn2AfterIm = squashSqliteScheme(sch2);
const validatedCurAfterImport = sqliteSchema.parse(sch2);
const {
sqlStatements: afterFileSqlStatements,
statements: afterFileStatements,
} = await applySqliteSnapshotsDiff(
sn2AfterIm,
initSn,
testTablesResolver(new Set()),
testColumnsResolver(new Set()),
testViewsResolverSqlite(new Set()),
validatedCurAfterImport,
validatedCur,
);
fs.rmSync(`tests/introspect/sqlite/${testName}.ts`);
return {
sqlStatements: afterFileSqlStatements,
statements: afterFileStatements,
};
};
export const introspectLibSQLToFile = async (
client: Client,
initSchema: SqliteSchema,
testName: string,
casing?: CasingType | undefined,
) => {
// put in db
const { sqlStatements } = await applyLibSQLDiffs(initSchema);
for (const st of sqlStatements) {
client.execute(st);
}
// introspect to schema
const introspectedSchema = await fromSqliteDatabase(
{
query: async <T>(sql: string, params: any[] = []) => {
return (await client.execute({ sql, args: params })).rows as T[];
},
run: async (query: string) => {
client.execute(query);
},
},
undefined,
);
const { version: initV, dialect: initD, ...initRest } = introspectedSchema;
const initSch = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...initRest,
} as const;
const initSn = squashSqliteScheme(initSch);
const validatedCur = sqliteSchema.parse(initSch);
const file = schemaToTypeScriptSQLite(introspectedSchema, 'camel');
fs.writeFileSync(`tests/introspect/libsql/${testName}.ts`, file.file);
const response = await prepareFromSqliteImports([
`tests/introspect/libsql/${testName}.ts`,
]);
const afterFileImports = generateSqliteSnapshot(
response.tables,
response.views,
casing,
);
const { version: v2, dialect: d2, ...rest2 } = afterFileImports;
const sch2 = {
version: '6',
dialect: 'sqlite',
id: '0',
prevId: '0',
...rest2,
} as const;
const sn2AfterIm = squashSqliteScheme(sch2);
const validatedCurAfterImport = sqliteSchema.parse(sch2);
const {
sqlStatements: afterFileSqlStatements,
statements: afterFileStatements,
} = await applyLibSQLSnapshotsDiff(
sn2AfterIm,
initSn,
testTablesResolver(new Set()),
testColumnsResolver(new Set()),
testViewsResolverSqlite(new Set()),
validatedCurAfterImport,
validatedCur,
);
fs.rmSync(`tests/introspect/libsql/${testName}.ts`);
return {
sqlStatements: afterFileSqlStatements,
statements: afterFileStatements,
};
};