Skip to content

Commit

Permalink
Merge pull request #1546 from Shelf-nu/fix-prisma-schema
Browse files Browse the repository at this point in the history
fix: prisma schema mismatch with DB
  • Loading branch information
DonKoko authored Dec 30, 2024
2 parents 91dd31b + 05311cc commit 1099618
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 4 deletions.
4 changes: 4 additions & 0 deletions app/database/post-migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { protectIndexesInMigration } from "./protected-indexes";

// This can be run as part of your migration pipeline
protectIndexesInMigration();
85 changes: 85 additions & 0 deletions app/database/protected-indexes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* eslint-disable no-console */
/**
* Configuration and utilities for protecting specific PostgreSQL indexes from being dropped during Prisma migrations.
* This is necessary because Prisma attempts to drop certain many-to-many relationship indexes that we want to maintain.
*/

import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";

/**
* Indexes that should be protected from being dropped during migrations.
* These are crucial for performance in many-to-many relationship queries.
*
* _AssetToBooking_Asset_idx: Optimizes booking availability checks
* _AssetToTag_asset_idx: Optimizes tag filtering operations
*/
const PROTECTED_INDEXES = [
"_AssetToBooking_Asset_idx", // Critical for booking availability checks
"_AssetToTag_asset_idx", // Critical for tag filtering performance
] as const;

/**
* Processes a newly created migration file to remove any DROP INDEX statements
* for protected indexes. This ensures our critical indexes remain in place.
*
* @throws {Error} If the migrations directory cannot be read or if migration files cannot be processed
*/
export function protectIndexesInMigration(): void {
try {
// Get the directory path using ESM compatible approach
const currentFilePath = fileURLToPath(import.meta.url);
const currentDir = path.dirname(currentFilePath);
const migrationsDir = path.join(currentDir, "migrations");

const migrations = fs
.readdirSync(migrationsDir)
.filter((file) =>
fs.statSync(path.join(migrationsDir, file)).isDirectory()
)
.sort((a, b) => b.localeCompare(a)); // Get latest migration first

const latestMigration = migrations[0];
if (!latestMigration) {
console.log("No migrations found to process");
return;
}

const migrationPath = path.join(
migrationsDir,
latestMigration,
"migration.sql"
);
if (!fs.existsSync(migrationPath)) {
console.log(`No migration file found in ${latestMigration}`);
return;
}

let content = fs.readFileSync(migrationPath, "utf8");
let modified = false;

PROTECTED_INDEXES.forEach((index) => {
const dropIndexPattern = new RegExp(
`-- DropIndex\\s*DROP INDEX (?:IF EXISTS )?["']?${index}["']?;\\s*`,
"gi"
);

if (dropIndexPattern.test(content)) {
content = content.replace(dropIndexPattern, "");
modified = true;
console.log(`Protected index ${index} from being dropped`);
}
});

if (modified) {
fs.writeFileSync(migrationPath, content);
console.log(`Successfully processed migration ${latestMigration}`);
} else {
console.log("No protected indexes were found in drop statements");
}
} catch (error) {
console.error("Failed to process migration for protected indexes:", error);
throw error;
}
}
22 changes: 21 additions & 1 deletion app/database/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,18 @@ model Asset {
customFields AssetCustomFieldValue[]
bookings Booking[]
//@@unique([title, organizationId]) //prisma doesnt support case insensitive unique index yet
// Special GIN index for optimization of simple search queries
@@index([title(ops: raw("gin_trgm_ops")), description(ops: raw("gin_trgm_ops"))], type: Gin)
// Indexes for optimization of queries
@@index([organizationId, title, status, availableToBook], name: "Asset_organizationId_compound_idx")
@@index([status, organizationId], name: "Asset_status_organizationId_idx")
@@index([createdAt, organizationId], name: "Asset_createdAt_organizationId_idx")
@@index([valuation, organizationId], name: "Asset_valuation_organizationId_idx")
@@index([categoryId, organizationId], name: "Asset_categoryId_organizationId_idx")
@@index([locationId, organizationId], name: "Asset_locationId_organizationId_idx")
@@index([kitId, organizationId], name: "Asset_kitId_organizationId_idx")
}


Expand Down Expand Up @@ -262,6 +273,8 @@ model Qr {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
scans Scan[]
@@index([assetId], name: "Qr_assetId_idx")
}

model PrintBatch {
Expand Down Expand Up @@ -388,6 +401,9 @@ model TeamMember {
updatedAt DateTime @updatedAt
deletedAt DateTime?
bookings Booking[]
// Special GIN index for optimization of simple search queries
@@index([name(ops: raw("gin_trgm_ops"))], type: Gin)
}

model Custody {
Expand All @@ -402,6 +418,8 @@ model Custody {
// DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([assetId, teamMemberId], name: "Custody_assetId_teamMemberId_idx")
}

model Organization {
Expand Down Expand Up @@ -585,6 +603,8 @@ model AssetCustomFieldValue {
// Datetime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([assetId, customFieldId], name: "AssetCustomFieldValue_lookup_idx")
}

enum Currency {
Expand Down
4 changes: 2 additions & 2 deletions app/routes/_welcome+/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export default function OnboardingLayout() {
useCrisp();

return (
<div className="flex min-h-screen flex-col md:h-full ">
<main className="relative flex size-full">
<div className="relative flex min-h-screen flex-col justify-center md:h-full">
<main className="flex size-full">
<div className="flex size-full flex-col items-center justify-center p-6 lg:p-10">
<div className="w-full rounded-xl bg-white shadow-xl md:w-[560px]">
<Outlet />
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ Here you can find the documentation for shelf.
- [Single sign-on](./sso/README.md) - Single sign-on powered by Supabase
- [Docker](./docker.md) - Run shelf via docker
- [Hooks](./hooks.md) - Shelf's utility hooks
- [DB: Protected indexes](./protected-indexes.md) - Shelf's utility hooks
- [URL Shortener](./url-shortener.md) - Shelf's url shortener for QR codes
- [Contributing](../CONTRIBUTING.md) - Contributing to Shelf.
23 changes: 23 additions & 0 deletions docs/protected-indexes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Protected Database Indexes

## Overview

This project maintains certain critical indexes that Prisma attempts to drop during migrations. We have implemented an automated protection system to prevent this from happening.

## Protected Indexes

- `_AssetToBooking_Asset_idx`: Optimizes queries for checking booking availability
- `_AssetToTag_asset_idx`: Optimizes asset filtering by tags

## How It Works

1. When Prisma generates a new migration, our post-migration script automatically removes any DROP INDEX statements for these protected indexes
2. This ensures the indexes remain in place while allowing Prisma migrations to proceed normally

## Important Notes

- Never manually drop these indexes
- If you need to modify these indexes, update the PROTECTED_INDEXES array in `prisma/protected-indexes.ts`
- The protection script runs automatically after `prisma migrate dev` and `prisma migrate deploy`

To find more information about the solution we have implemented you can refer to the PR that made the change [#1546](https://github.com/Shelf-nu/shelf.nu/pull/1546)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"build": "NODE_ENV=production remix vite:build",
"db:diff": "prisma migrate diff --from-url=${DATABASE_URL} --to-migrations=./app/database/migrations --shadow-database-url=${SHADOW_DATABASE_URL} --script > rollback.sql",
"db:prepare-migration": "prisma generate && prisma migrate dev --create-only --skip-seed",
"db:prepare-migration": "prisma generate && prisma migrate dev --create-only --skip-seed && tsx app/database/post-migration.ts",
"db:generate-type": "prisma generate",
"db:deploy": "prisma migrate deploy",
"db:deploy-migration": "prisma migrate deploy && prisma generate",
Expand Down

0 comments on commit 1099618

Please sign in to comment.