0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-02-03 22:29:08 -05:00

feat: index migrations

This commit is contained in:
bholmesdev 2024-01-30 11:58:54 -05:00
parent 8f342b0db7
commit 84e5bd0830
3 changed files with 77 additions and 23 deletions

View file

@ -1,4 +1,5 @@
import * as color from 'kleur/colors';
import deepDiff from 'deep-diff';
import type {
BooleanField,
DBCollection,
@ -8,6 +9,7 @@ import type {
DBSnapshot,
DateField,
FieldType,
Indexes,
JsonField,
NumberField,
TextField,
@ -18,6 +20,7 @@ import prompts from 'prompts';
import {
getCreateTableQuery,
getModifiers,
getCreateIndexQueries,
hasDefault,
hasPrimaryKey,
schemaTypeToSqlType,
@ -62,6 +65,7 @@ export async function getMigrationQueries({
for (const [collectionName, collection] of Object.entries(added)) {
queries.push(getCreateTableQuery(collectionName, collection));
queries.push(...getCreateIndexQueries(collectionName, collection));
}
for (const [collectionName] of Object.entries(dropped)) {
@ -96,11 +100,15 @@ export async function getCollectionChangeQueries({
}): Promise<string[]> {
const queries: string[] = [];
const updated = getUpdatedFields(oldCollection.fields, newCollection.fields);
let added = getAddedFields(oldCollection.fields, newCollection.fields);
let dropped = getDroppedFields(oldCollection.fields, newCollection.fields);
let added = getAdded(oldCollection.fields, newCollection.fields);
let dropped = getDropped(oldCollection.fields, newCollection.fields);
if (isEmpty(updated) && isEmpty(added) && isEmpty(dropped)) {
return [];
return getChangeIndexQueries({
collectionName,
oldIndexes: oldCollection.indexes,
newIndexes: newCollection.indexes,
});
}
if (!isEmpty(added) && !isEmpty(dropped)) {
const resolved = await resolveFieldRenames(
@ -118,7 +126,14 @@ export async function getCollectionChangeQueries({
Object.values(dropped).every(canAlterTableDropColumn) &&
Object.values(added).every(canAlterTableAddColumn)
) {
queries.push(...getAlterTableQueries(collectionName, added, dropped));
queries.push(
...getAlterTableQueries(collectionName, added, dropped),
...getChangeIndexQueries({
collectionName,
oldIndexes: oldCollection.indexes,
newIndexes: newCollection.indexes,
})
);
return queries;
}
@ -158,7 +173,7 @@ export async function getCollectionChangeQueries({
allowDataLoss = !!res.allowDataLoss;
}
if (!allowDataLoss) {
console.log('Exiting without changes 👋');
console.info('Exiting without changes 👋');
process.exit(0);
}
}
@ -175,7 +190,32 @@ export async function getCollectionChangeQueries({
hasDataLoss: dataLossCheck.dataLoss,
migrateHiddenPrimaryKey: !addedPrimaryKey && !droppedPrimaryKey && !updatedPrimaryKey,
});
queries.push(...recreateTableQueries);
queries.push(...recreateTableQueries, ...getCreateIndexQueries(collectionName, newCollection));
return queries;
}
function getChangeIndexQueries({
collectionName,
oldIndexes = {},
newIndexes = {},
}: {
collectionName: string;
oldIndexes?: Indexes;
newIndexes?: Indexes;
}) {
const added = getAdded(oldIndexes, newIndexes);
const dropped = getDropped(oldIndexes, newIndexes);
const updated = getUpdated(oldIndexes, newIndexes);
Object.assign(dropped, updated);
Object.assign(added, updated);
const queries: string[] = [];
for (const indexName of Object.keys(dropped)) {
const dropQuery = `DROP INDEX ${sqlite.escapeName(indexName)}`;
queries.push(dropQuery);
}
queries.push(...getCreateIndexQueries(collectionName, { indexes: added }));
return queries;
}
@ -454,22 +494,32 @@ function canRecreateTableWithoutDataLoss(
return { dataLoss: false };
}
function getAddedFields(oldFields: DBFields, newFields: DBFields) {
const added: DBFields = {};
for (const [key, newField] of Object.entries(newFields)) {
if (!(key in oldFields)) added[key] = newField;
function getAdded<T>(oldObj: Record<string, T>, newObj: Record<string, T>) {
const added: Record<string, T> = {};
for (const [key, value] of Object.entries(newObj)) {
if (!(key in oldObj)) added[key] = value;
}
return added;
}
function getDroppedFields(oldFields: DBFields, newFields: DBFields) {
const dropped: DBFields = {};
for (const [key, oldField] of Object.entries(oldFields)) {
if (!(key in newFields)) dropped[key] = oldField;
function getDropped<T>(oldObj: Record<string, T>, newObj: Record<string, T>) {
const dropped: Record<string, T> = {};
for (const [key, value] of Object.entries(oldObj)) {
if (!(key in newObj)) dropped[key] = value;
}
return dropped;
}
function getUpdated<T>(oldObj: Record<string, T>, newObj: Record<string, T>) {
const updated: Record<string, T> = {};
for (const [key, value] of Object.entries(newObj)) {
const oldValue = oldObj[key];
if (!oldValue) continue;
if (deepDiff(oldValue, value)) updated[key] = value;
}
return updated;
}
type UpdatedFields = Record<string, { old: DBField; new: DBField }>;
function getUpdatedFields(oldFields: DBFields, newFields: DBFields): UpdatedFields {
@ -479,6 +529,7 @@ function getUpdatedFields(oldFields: DBFields, newFields: DBFields): UpdatedFiel
if (!oldField) continue;
if (objShallowEqual(oldField, newField)) continue;
// TODO: refactor to deep-diff with a prefilter on `type`
const oldFieldSqlType = { ...oldField, type: schemaTypeToSqlType(oldField.type) };
const newFieldSqlType = { ...newField, type: schemaTypeToSqlType(newField.type) };
const isSafeTypeUpdate =

View file

@ -101,8 +101,8 @@ export async function setupDbTables({
for (const [name, collection] of Object.entries(collections)) {
const dropQuery = sql.raw(`DROP TABLE IF EXISTS ${name}`);
const createQuery = sql.raw(getCreateTableQuery(name, collection));
const indexQueries = getTableIndexQueries(name, collection);
setupQueries.push(dropQuery, createQuery, ...indexQueries);
const indexQueries = getCreateIndexQueries(name, collection);
setupQueries.push(dropQuery, createQuery, ...indexQueries.map((s) => sql.raw(s)));
}
for (const q of setupQueries) {
await db.run(q);
@ -160,18 +160,19 @@ export function getCreateTableQuery(collectionName: string, collection: DBCollec
return query;
}
export function getTableIndexQueries(collectionName: string, collection: DBCollection) {
let queries: SQL[] = [];
export function getCreateIndexQueries(
collectionName: string,
collection: Pick<DBCollection, 'indexes'>
) {
let queries: string[] = [];
for (const [indexName, indexProps] of Object.entries(collection.indexes ?? {})) {
const onColNames = Array.isArray(indexProps.on) ? indexProps.on : [indexProps.on];
const onCols = onColNames.map((colName) => sqlite.escapeName(colName));
const unique = indexProps.unique ? 'UNIQUE ' : '';
const indexQuery = sql.raw(
`CREATE ${unique}INDEX ${sqlite.escapeName(indexName)} ON ${sqlite.escapeName(
collectionName
)} (${onCols.join(', ')})`
);
const indexQuery = `CREATE ${unique}INDEX ${sqlite.escapeName(
indexName
)} ON ${sqlite.escapeName(collectionName)} (${onCols.join(', ')})`;
queries.push(indexQuery);
}
return queries;

View file

@ -58,6 +58,8 @@ export const indexSchema = z.object({
});
const indexesSchema = z.record(indexSchema);
export type Indexes = z.infer<typeof indexesSchema>;
const baseCollectionSchema = z.object({
fields: fieldsSchema,
indexes: indexesSchema.optional(),