Skip to content

Commit

Permalink
fix: Issue with matching names when overwritting field names to @Map
Browse files Browse the repository at this point in the history
…names (#115)

If field names were equal in different models, for example `id`, and you had an `@map` to overwrite that default field name, the generator would grab the first name instead of the correct name in each model.
  • Loading branch information
keonik authored Jul 26, 2022
1 parent 97c8213 commit 6ffc13f
Show file tree
Hide file tree
Showing 12 changed files with 7,573 additions and 18,723 deletions.
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="postgresql://johndoe:[email protected]:6543/mydb?schema=public"
ERD_DEBUG=true

<!-- ERD_DEBUG=true -->
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
.DS_Store
node_modules
dist
prisma.mmd
prisma.mmd
prisma/debug
2 changes: 1 addition & 1 deletion ERD.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,25 @@ DISABLE_ERD=true

### Debugging

If you have issues or are contributing to this repository, you may benefit from seeing logs of the steps to making your ERD. Enable debugging by adding the following environment variable and re-running `prisma generate`.
If you have issues with generating or outputting an ERD as expected, you may benefit from seeing output of the steps to making your ERD. Enable debugging by either adding the following environment variable

```bash
ERD_DEBUG=true
```

or adding in the debug configuration key set to `true`

```prisma
generator erd {
provider = "prisma-erd-generator"
erdDebug = true
}
```

and re-running `prisma generate`. You should see a directory and files created labeling the steps to create an ER diagram under `prisma/debug`.

Please use these files as part of opening an issue if you run into problems.

### Table only mode

Table mode only draws your models and skips the attributes and columns associated with your table. This feature is helpful for when you have lots of table columns and they are less helpful than seeing the tables and their relationships
Expand Down
1 change: 1 addition & 0 deletions __tests__/114.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion __tests__/73.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion __tests__/Mappings.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions __tests__/issues/114.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as child_process from 'child_process';

test('space removal in mapped field names', async () => {
const fileName = '114.svg';
const folderName = '__tests__';
child_process.execSync(`rm -f ${folderName}/${fileName}`);
child_process.execSync(
`npx prisma generate --schema ./prisma/issues/114.prisma`
);
const svgContent = child_process
.execSync(`cat ${folderName}/${fileName}`)
.toString();
// did the model get added
expect(svgContent).toContain('orders');
expect(svgContent).toContain('customers');
expect(svgContent).toContain('employees');
expect(svgContent).toContain('stores');
// did the field mapped names get added
expect(svgContent).toContain('customer_id');
expect(svgContent).toContain('store_id');
expect(svgContent).toContain('employee_id');
expect(svgContent).toContain('order_id');
});
26,092 changes: 7,417 additions & 18,675 deletions package-lock.json

Large diffs are not rendered by default.

29 changes: 16 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
"keywords": [
"Prisma",
"TypeScript",
"Mermaid"
"Mermaid",
"Entity Relationship Diagram",
"ERD"
],
"contributors": [
{
Expand All @@ -44,18 +46,18 @@
}
],
"devDependencies": {
"@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"@babel/core": "^7.18.9",
"@babel/preset-env": "^7.18.9",
"@babel/preset-typescript": "^7.18.6",
"@types/jest": "^28.1.4",
"@types/jest": "^28.1.6",
"all-contributors-cli": "^6.20.0",
"babel-jest": "^28.1.2",
"concurrently": "^7.2.2",
"babel-jest": "^28.1.3",
"concurrently": "^7.3.0",
"husky": "^8.0.1",
"jest": "^28.1.2",
"jest": "^28.1.3",
"lint-staged": "^13.0.3",
"prettier": "2.7.1",
"prisma": "^4.0.0",
"prisma": "^4.1.0",
"standard-version": "^9.5.0",
"tslib": "^2.4.0",
"typescript": "^4.7.4"
Expand All @@ -65,14 +67,15 @@
"**/*.ts": "npm run test"
},
"dependencies": {
"@mermaid-js/mermaid-cli": "^9.1.3",
"@prisma/client": "^4.0.0",
"@prisma/generator-helper": "^4.0.0",
"@mermaid-js/mermaid-cli": "^9.1.4",
"@prisma/client": "^4.1.0",
"@prisma/generator-helper": "^4.1.0",
"dotenv": "^16.0.1"
},
"peerDependencies": {
"@mermaid-js/mermaid-cli": "^9.1.3",
"@mermaid-js/mermaid-cli": "^9.1.4",
"@prisma/client": "^4.0.0",
"@prisma/generator-helper": "^4.0.0"
}
},
"packageManager": "[email protected]"
}
49 changes: 49 additions & 0 deletions prisma/issues/114.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
generator erd {
provider = "node ./dist/index.js"
output = "../../__tests__/114.svg"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model Store {
id String @id @default(uuid()) @map("store_id")
employees Employee[]
customers Customer[]
@@map("stores")
}

model Employee {
id String @id @default(uuid()) @map("employee_id")
storeId String @map("store_id")
store Store @relation(fields: [storeId], references: [id])
orders Order[]
@@map("employees")
}

model Customer {
id String @id @default(uuid()) @map("customer_id")
storeId String @map("store_id")
store Store @relation(fields: [storeId], references: [id])
orders Order[]
@@map("customers")
}

model Order {
id String @id @default(uuid()) @map("order_id")
employeeId String @map("employee_id")
customerId String @map("customer_id")
employee Employee @relation(fields: [employeeId], references: [id])
customer Customer @relation(fields: [customerId], references: [id])
@@map("orders")
}
75 changes: 46 additions & 29 deletions src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,15 +182,14 @@ ${
)
)
// the replace is a hack to make MongoDB style ID columns like _id valid for Mermaid
.map(
(field) =>
` ${field.type.trimStart()} ${field.name.replace(
/^_/,
'z_'
)} ${field.isId ? 'PK' : ''} ${
field.isRequired ? '' : '"nullable"'
}`
)
.map((field) => {
return ` ${field.type.trimStart()} ${field.name.replace(
/^_/,
'z_'
)} ${field.isId ? 'PK' : ''} ${
field.isRequired ? '' : '"nullable"'
}`;
})
.join('\n')
}
}
Expand Down Expand Up @@ -294,31 +293,30 @@ ${
export const mapPrismaToDb = (dmlModels: DMLModel[], dataModel: string) => {
const splitDataModel = dataModel
?.split('\n')
.filter((line) => line.includes('@map'))
.filter((line) => line.includes('@map') || line.includes('model '))
.map((line) => line.trim());

return dmlModels.map((model) => {
return {
...model,
fields: model.fields.map((field) => {
// get line with field to \n
const lineInDataModel = splitDataModel.find((line) =>
line.includes(`${field.name}`)
);
const lineInDataModel = splitDataModel
// skip lines before the current model
.slice(
splitDataModel.findIndex((line) =>
line.includes(`model ${model.name}`)
)
)
.find((line) => line.includes(`${field.name}`));
if (lineInDataModel) {
const startingMapIndex =
lineInDataModel.indexOf('@map') + 6;
const modelField = lineInDataModel.substring(
startingMapIndex,
lineInDataModel
.substring(startingMapIndex)
.indexOf('")') + startingMapIndex
);
if (modelField) {
const regex = new RegExp(/@map\(\"(.*?)\"\)/, 'g');
const match = regex.exec(lineInDataModel);
if (match?.[1]) {
// remove spaces
field = {
...field,
name: modelField
name: match[1]
// replace leading underscores and spaces in @map column
.replace(/^_/, 'z_')
.replace(/\s/g, ''),
Expand All @@ -339,7 +337,8 @@ export default async (options: GeneratorOptions) => {
const theme = config.theme || 'forest';
const tableOnly = config.tableOnly === 'true';
const disabled = Boolean(process.env.DISABLE_ERD);
const debug = Boolean(process.env.ERD_DEBUG);
const debug =
config.erdDebug === 'true' || Boolean(process.env.ERD_DEBUG);

if (disabled) {
return console.log('ERD generator is disabled');
Expand All @@ -359,22 +358,40 @@ export default async (options: GeneratorOptions) => {
options.datamodel,
tmpDir
);
if (!datamodelString) throw new Error('could not parse datamodel');
if (debug && datamodelString)
console.log('datamodelString: ', datamodelString);
if (!datamodelString) {
throw new Error('could not parse datamodel');
}

if (debug && datamodelString) {
fs.mkdirSync(path.resolve('prisma/debug'), { recursive: true });
const dataModelFile = path.resolve('prisma/debug/1-datamodel.json');
fs.writeFileSync(dataModelFile, datamodelString);
console.log(`data model written to ${dataModelFile}`);
}

let dml: DML = JSON.parse(datamodelString);

// updating dml to map to db table and column names (@map && @@map)
dml.models = mapPrismaToDb(dml.models, options.datamodel);

// default types to empty array
if (!dml.types) {
dml.types = [];
}
if (debug && dml.models) console.log('mapped models: ', dml.models);
if (debug && dml.models) {
const mapAppliedFile = path.resolve(
'prisma/debug/2-datamodel-map-applied.json'
);
fs.writeFileSync(mapAppliedFile, JSON.stringify(dml, null, 2));
console.log(`applied @map to fields written to ${mapAppliedFile}`);
}

const mermaid = renderDml(dml, { tableOnly });
if (debug && mermaid) console.log('mermaid string: ', mermaid);
if (debug && mermaid) {
const mermaidFile = path.resolve('prisma/debug/3-mermaid.mmd');
fs.writeFileSync(mermaidFile, mermaid);
console.log(`mermaid written to ${mermaidFile}`);
}

if (!mermaid)
throw new Error('failed to construct mermaid instance from dml');
Expand Down

0 comments on commit 6ffc13f

Please sign in to comment.