Skip to content

Commit

Permalink
chore(db): Rename all collection usage to tables (#10460)
Browse files Browse the repository at this point in the history
* chore: rename `collection` field to `table`

* chore: remove deprecated ResolvedCollectionConfig type (only used by studio)

* chore: collection -> table in migration-queries

* chore: update tests

* chore: last renames

* chore: bump migration version

* chore: remove deprecated collection field

* chore: droptablequeries

* chore(test): collection -> tables

* chore: revert collection -> table change on migration file

* chore: revert migration version change

* chore: changeset
  • Loading branch information
bholmesdev authored Mar 27, 2024
1 parent 06a6c1a commit 713abb2
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 154 deletions.
5 changes: 5 additions & 0 deletions .changeset/yellow-turtles-rescue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@astrojs/db": patch
---

Remove legacy Astro DB internals using the "collections" naming convention instead of "tables."
150 changes: 71 additions & 79 deletions packages/db/src/core/cli/migration-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,82 +57,77 @@ export async function getMigrationQueries({
queries.push(...getDropTableQueriesForSnapshot(currentSnapshot));
}

const addedCollections = getAddedCollections(oldSnapshot, newSnapshot);
const droppedTables = getDroppedCollections(oldSnapshot, newSnapshot);
const addedTables = getAddedTables(oldSnapshot, newSnapshot);
const droppedTables = getDroppedTables(oldSnapshot, newSnapshot);
const notDeprecatedDroppedTables = Object.fromEntries(
Object.entries(droppedTables).filter(([, table]) => !table.deprecated)
);
if (!isEmpty(addedCollections) && !isEmpty(notDeprecatedDroppedTables)) {
if (!isEmpty(addedTables) && !isEmpty(notDeprecatedDroppedTables)) {
throw new Error(
RENAME_TABLE_ERROR(
Object.keys(addedCollections)[0],
Object.keys(notDeprecatedDroppedTables)[0]
)
RENAME_TABLE_ERROR(Object.keys(addedTables)[0], Object.keys(notDeprecatedDroppedTables)[0])
);
}

for (const [collectionName, collection] of Object.entries(addedCollections)) {
queries.push(getCreateTableQuery(collectionName, collection));
queries.push(...getCreateIndexQueries(collectionName, collection));
for (const [tableName, table] of Object.entries(addedTables)) {
queries.push(getCreateTableQuery(tableName, table));
queries.push(...getCreateIndexQueries(tableName, table));
}

for (const [collectionName] of Object.entries(droppedTables)) {
const dropQuery = `DROP TABLE ${sqlite.escapeName(collectionName)}`;
for (const [tableName] of Object.entries(droppedTables)) {
const dropQuery = `DROP TABLE ${sqlite.escapeName(tableName)}`;
queries.push(dropQuery);
}

for (const [collectionName, newCollection] of Object.entries(newSnapshot.schema)) {
const oldCollection = oldSnapshot.schema[collectionName];
if (!oldCollection) continue;
const addedColumns = getAdded(oldCollection.columns, newCollection.columns);
const droppedColumns = getDropped(oldCollection.columns, newCollection.columns);
for (const [tableName, newTable] of Object.entries(newSnapshot.schema)) {
const oldTable = oldSnapshot.schema[tableName];
if (!oldTable) continue;
const addedColumns = getAdded(oldTable.columns, newTable.columns);
const droppedColumns = getDropped(oldTable.columns, newTable.columns);
const notDeprecatedDroppedColumns = Object.fromEntries(
Object.entries(droppedColumns).filter(([, col]) => !col.schema.deprecated)
);
if (!isEmpty(addedColumns) && !isEmpty(notDeprecatedDroppedColumns)) {
throw new Error(
RENAME_COLUMN_ERROR(
`${collectionName}.${Object.keys(addedColumns)[0]}`,
`${collectionName}.${Object.keys(notDeprecatedDroppedColumns)[0]}`
`${tableName}.${Object.keys(addedColumns)[0]}`,
`${tableName}.${Object.keys(notDeprecatedDroppedColumns)[0]}`
)
);
}
const result = await getCollectionChangeQueries({
collectionName,
oldCollection,
newCollection,
const result = await getTableChangeQueries({
tableName,
oldTable,
newTable,
});
queries.push(...result.queries);
confirmations.push(...result.confirmations);
}
return { queries, confirmations };
}

export async function getCollectionChangeQueries({
collectionName,
oldCollection,
newCollection,
export async function getTableChangeQueries({
tableName,
oldTable,
newTable,
}: {
collectionName: string;
oldCollection: DBTable;
newCollection: DBTable;
tableName: string;
oldTable: DBTable;
newTable: DBTable;
}): Promise<{ queries: string[]; confirmations: string[] }> {
const queries: string[] = [];
const confirmations: string[] = [];
const updated = getUpdatedColumns(oldCollection.columns, newCollection.columns);
const added = getAdded(oldCollection.columns, newCollection.columns);
const dropped = getDropped(oldCollection.columns, newCollection.columns);
const updated = getUpdatedColumns(oldTable.columns, newTable.columns);
const added = getAdded(oldTable.columns, newTable.columns);
const dropped = getDropped(oldTable.columns, newTable.columns);
/** Any foreign key changes require a full table recreate */
const hasForeignKeyChanges = Boolean(
deepDiff(oldCollection.foreignKeys, newCollection.foreignKeys)
);
const hasForeignKeyChanges = Boolean(deepDiff(oldTable.foreignKeys, newTable.foreignKeys));

if (!hasForeignKeyChanges && isEmpty(updated) && isEmpty(added) && isEmpty(dropped)) {
return {
queries: getChangeIndexQueries({
collectionName,
oldIndexes: oldCollection.indexes,
newIndexes: newCollection.indexes,
tableName,
oldIndexes: oldTable.indexes,
newIndexes: newTable.indexes,
}),
confirmations,
};
Expand All @@ -145,11 +140,11 @@ export async function getCollectionChangeQueries({
Object.values(added).every(canAlterTableAddColumn)
) {
queries.push(
...getAlterTableQueries(collectionName, added, dropped),
...getAlterTableQueries(tableName, added, dropped),
...getChangeIndexQueries({
collectionName,
oldIndexes: oldCollection.indexes,
newIndexes: newCollection.indexes,
tableName,
oldIndexes: oldTable.indexes,
newIndexes: newTable.indexes,
})
);
return { queries, confirmations };
Expand All @@ -160,37 +155,37 @@ export async function getCollectionChangeQueries({
const { reason, columnName } = dataLossCheck;
const reasonMsgs: Record<DataLossReason, string> = {
'added-required': `You added new required column '${color.bold(
collectionName + '.' + columnName
tableName + '.' + columnName
)}' with no default value.\n This cannot be executed on an existing table.`,
'updated-type': `Updating existing column ${color.bold(
collectionName + '.' + columnName
tableName + '.' + columnName
)} to a new type that cannot be handled automatically.`,
};
confirmations.push(reasonMsgs[reason]);
}

const primaryKeyExists = Object.entries(newCollection.columns).find(([, column]) =>
const primaryKeyExists = Object.entries(newTable.columns).find(([, column]) =>
hasPrimaryKey(column)
);
const droppedPrimaryKey = Object.entries(dropped).find(([, column]) => hasPrimaryKey(column));

const recreateTableQueries = getRecreateTableQueries({
collectionName,
newCollection,
tableName,
newTable,
added,
hasDataLoss: dataLossCheck.dataLoss,
migrateHiddenPrimaryKey: !primaryKeyExists && !droppedPrimaryKey,
});
queries.push(...recreateTableQueries, ...getCreateIndexQueries(collectionName, newCollection));
queries.push(...recreateTableQueries, ...getCreateIndexQueries(tableName, newTable));
return { queries, confirmations };
}

function getChangeIndexQueries({
collectionName,
tableName,
oldIndexes = {},
newIndexes = {},
}: {
collectionName: string;
tableName: string;
oldIndexes?: Indexes;
newIndexes?: Indexes;
}) {
Expand All @@ -206,22 +201,22 @@ function getChangeIndexQueries({
const dropQuery = `DROP INDEX ${sqlite.escapeName(indexName)}`;
queries.push(dropQuery);
}
queries.push(...getCreateIndexQueries(collectionName, { indexes: added }));
queries.push(...getCreateIndexQueries(tableName, { indexes: added }));
return queries;
}

function getAddedCollections(oldCollections: DBSnapshot, newCollections: DBSnapshot): DBTables {
function getAddedTables(oldTables: DBSnapshot, newTables: DBSnapshot): DBTables {
const added: DBTables = {};
for (const [key, newCollection] of Object.entries(newCollections.schema)) {
if (!(key in oldCollections.schema)) added[key] = newCollection;
for (const [key, newTable] of Object.entries(newTables.schema)) {
if (!(key in oldTables.schema)) added[key] = newTable;
}
return added;
}

function getDroppedCollections(oldCollections: DBSnapshot, newCollections: DBSnapshot): DBTables {
function getDroppedTables(oldTables: DBSnapshot, newTables: DBSnapshot): DBTables {
const dropped: DBTables = {};
for (const [key, oldCollection] of Object.entries(oldCollections.schema)) {
if (!(key in newCollections.schema)) dropped[key] = oldCollection;
for (const [key, oldTable] of Object.entries(oldTables.schema)) {
if (!(key in newTables.schema)) dropped[key] = oldTable;
}
return dropped;
}
Expand All @@ -231,17 +226,17 @@ function getDroppedCollections(oldCollections: DBSnapshot, newCollections: DBSna
* `canUseAlterTableAddColumn` and `canAlterTableDropColumn` checks!
*/
function getAlterTableQueries(
unescapedCollectionName: string,
unescTableName: string,
added: DBColumns,
dropped: DBColumns
): string[] {
const queries: string[] = [];
const collectionName = sqlite.escapeName(unescapedCollectionName);
const tableName = sqlite.escapeName(unescTableName);

for (const [unescColumnName, column] of Object.entries(added)) {
const columnName = sqlite.escapeName(unescColumnName);
const type = schemaTypeToSqlType(column.type);
const q = `ALTER TABLE ${collectionName} ADD COLUMN ${columnName} ${type}${getModifiers(
const q = `ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${type}${getModifiers(
columnName,
column
)}`;
Expand All @@ -250,37 +245,34 @@ function getAlterTableQueries(

for (const unescColumnName of Object.keys(dropped)) {
const columnName = sqlite.escapeName(unescColumnName);
const q = `ALTER TABLE ${collectionName} DROP COLUMN ${columnName}`;
const q = `ALTER TABLE ${tableName} DROP COLUMN ${columnName}`;
queries.push(q);
}

return queries;
}

function getRecreateTableQueries({
collectionName: unescCollectionName,
newCollection,
tableName: unescTableName,
newTable,
added,
hasDataLoss,
migrateHiddenPrimaryKey,
}: {
collectionName: string;
newCollection: DBTable;
tableName: string;
newTable: DBTable;
added: Record<string, DBColumn>;
hasDataLoss: boolean;
migrateHiddenPrimaryKey: boolean;
}): string[] {
const unescTempName = `${unescCollectionName}_${genTempTableName()}`;
const unescTempName = `${unescTableName}_${genTempTableName()}`;
const tempName = sqlite.escapeName(unescTempName);
const collectionName = sqlite.escapeName(unescCollectionName);
const tableName = sqlite.escapeName(unescTableName);

if (hasDataLoss) {
return [
`DROP TABLE ${collectionName}`,
getCreateTableQuery(unescCollectionName, newCollection),
];
return [`DROP TABLE ${tableName}`, getCreateTableQuery(unescTableName, newTable)];
}
const newColumns = [...Object.keys(newCollection.columns)];
const newColumns = [...Object.keys(newTable.columns)];
if (migrateHiddenPrimaryKey) {
newColumns.unshift('_id');
}
Expand All @@ -290,10 +282,10 @@ function getRecreateTableQueries({
.join(', ');

return [
getCreateTableQuery(unescTempName, newCollection),
`INSERT INTO ${tempName} (${escapedColumns}) SELECT ${escapedColumns} FROM ${collectionName}`,
`DROP TABLE ${collectionName}`,
`ALTER TABLE ${tempName} RENAME TO ${collectionName}`,
getCreateTableQuery(unescTempName, newTable),
`INSERT INTO ${tempName} (${escapedColumns}) SELECT ${escapedColumns} FROM ${tableName}`,
`DROP TABLE ${tableName}`,
`ALTER TABLE ${tempName} RENAME TO ${tableName}`,
];
}

Expand Down Expand Up @@ -462,8 +454,8 @@ export async function getProductionCurrentSnapshot({

function getDropTableQueriesForSnapshot(snapshot: DBSnapshot) {
const queries = [];
for (const collectionName of Object.keys(snapshot.schema)) {
const dropQuery = `DROP TABLE ${sqlite.escapeName(collectionName)}`;
for (const tableName of Object.keys(snapshot.schema)) {
const dropQuery = `DROP TABLE ${sqlite.escapeName(tableName)}`;
queries.unshift(dropQuery);
}
return queries;
Expand Down
6 changes: 3 additions & 3 deletions packages/db/src/core/integration/typegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function typegenInternal({ tables, root }: { tables: DBTables; root
const content = `// This file is generated by Astro DB
declare module 'astro:db' {
${Object.entries(tables)
.map(([name, collection]) => generateTableType(name, collection))
.map(([name, table]) => generateTableType(name, table))
.join('\n')}
}
`;
Expand All @@ -30,8 +30,8 @@ ${Object.entries(tables)
await writeFile(new URL(DB_TYPES_FILE, dotAstroDir), content);
}

function generateTableType(name: string, collection: DBTable): string {
const sanitizedColumnsList = Object.entries(collection.columns)
function generateTableType(name: string, table: DBTable): string {
const sanitizedColumnsList = Object.entries(table.columns)
// Filter out deprecated columns from the typegen, so that they don't
// appear as queryable fields in the generated types / your codebase.
.filter(([, val]) => !val.schema.deprecated);
Expand Down
10 changes: 5 additions & 5 deletions packages/db/src/core/integration/vite-plugin-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ ${
export * from ${RUNTIME_CONFIG_IMPORT};
${getStringifiedCollectionExports(tables)}`;
${getStringifiedTableExports(tables)}`;
}

export function getStudioVirtualModContents({
Expand Down Expand Up @@ -169,16 +169,16 @@ export const db = await createRemoteDatabaseClient(${appTokenArg()}, ${dbUrlArg(
export * from ${RUNTIME_CONFIG_IMPORT};
${getStringifiedCollectionExports(tables)}
${getStringifiedTableExports(tables)}
`;
}

function getStringifiedCollectionExports(tables: DBTables) {
function getStringifiedTableExports(tables: DBTables) {
return Object.entries(tables)
.map(
([name, collection]) =>
([name, table]) =>
`export const ${name} = asDrizzleTable(${JSON.stringify(name)}, ${JSON.stringify(
collection
table
)}, false)`
)
.join('\n');
Expand Down
4 changes: 2 additions & 2 deletions packages/db/src/core/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ const baseColumnSchema = z.object({
unique: z.boolean().optional().default(false),
deprecated: z.boolean().optional().default(false),

// Defined when `defineReadableTable()` is called
// Defined when `defineDb()` is called to resolve `references`
name: z.string().optional(),
// TODO: rename to `tableName`. Breaking schema change
// TODO: Update to `table`. Will need migration file version change
collection: z.string().optional(),
});

Expand Down
4 changes: 0 additions & 4 deletions packages/db/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,6 @@ interface IndexConfig<TColumns extends ColumnsConfig> extends z.input<typeof ind
on: MaybeArray<Extract<keyof TColumns, string>>;
}

/** @deprecated Use `TableConfig` instead */
export type ResolvedCollectionConfig<TColumns extends ColumnsConfig = ColumnsConfig> =
TableConfig<TColumns>;

// We cannot use `Omit<NumberColumn | TextColumn, 'type'>`,
// since Omit collapses our union type on primary key.
export type NumberColumnOpts = z.input<typeof numberColumnOptsSchema>;
Expand Down
2 changes: 1 addition & 1 deletion packages/db/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type { ResolvedCollectionConfig, TableConfig } from './core/types.js';
export type { TableConfig } from './core/types.js';
export { cli } from './core/cli/index.js';
export { integration as default } from './core/integration/index.js';
export { typegen } from './core/integration/typegen.js';
Loading

0 comments on commit 713abb2

Please sign in to comment.