Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: improve polymorphism code generation speed #1073

Merged
merged 2 commits into from
Mar 4, 2024

Conversation

ymc9
Copy link
Member

@ymc9 ymc9 commented Mar 4, 2024

Summary by CodeRabbit

  • New Features
    • Introduced enhancements in schema processing, including new types and functions for transformation.
    • Added support for named constraints in schemas for various database providers.
  • Refactor
    • Improved the initialization process of schema generation tools for better efficiency and clarity.
  • Tests
    • Added regression tests for specific issues to ensure robustness.
    • Introduced a new test for TypeScript compilation with PrismaClient operations.

Copy link
Contributor

coderabbitai bot commented Mar 4, 2024

Walkthrough

Walkthrough

This update introduces significant enhancements to the schema and Prisma plugins, focusing on the addition of new types, functions, and support for named constraints across different database providers. It also modifies the initialization and generation processes for Prisma schemas, improving the handling of polymorphic associations and refining the structure and capabilities of the Prisma namespace. Integration tests have been updated to reflect these changes and address specific issues.

Changes

Files Change Summary
packages/schema/src/plugins/enhancer/enhance/index.ts Added DelegateInfo type, new functions, updated Prisma schema logic, and namespace transformations.
packages/schema/src/plugins/prisma/index.ts
.../plugins/enhancer/delegate/index.ts
.../prisma/schema-generator.ts
Modified PrismaSchemaGenerator initialization to accept model during construction, introduced zmodel parameter, and added support for named constraints for different DB providers.
tests/integration/tests/enhancements/.../issue-1058.test.ts
.../issue-1064.test.ts
.../polymorphism.test.ts
Updated descriptions, added regression tests, and new tests for TypeScript compilation with PrismaClient operations.

Related issues

  • [Feature Request] Support for Polymorphic Associations #430: The enhancements made in the Prisma plugin, particularly around the handling and support for polymorphic associations, could potentially address the objectives of this issue by enabling more flexible and abstract level access policies as requested.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share

Tips

Chat

There are 3 ways to chat with CodeRabbit:

Note: Auto-reply has been disabled for this repository by the repository owner. The CodeRabbit bot will not respond to your comments unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit tests for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 6

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 0ac596f and 64a52dc.
Files selected for processing (7)
  • packages/schema/src/plugins/enhancer/delegate/index.ts (1 hunks)
  • packages/schema/src/plugins/enhancer/enhance/index.ts (4 hunks)
  • packages/schema/src/plugins/prisma/index.ts (1 hunks)
  • packages/schema/src/plugins/prisma/schema-generator.ts (7 hunks)
  • tests/integration/tests/enhancements/with-delegate/issue-1058.test.ts (1 hunks)
  • tests/integration/tests/enhancements/with-delegate/issue-1064.test.ts (1 hunks)
  • tests/integration/tests/enhancements/with-delegate/polymorphism.test.ts (2 hunks)
Additional comments: 10
packages/schema/src/plugins/prisma/index.ts (1)
  • 8-8: The modification to pass model directly to the PrismaSchemaGenerator constructor simplifies the generator's usage and aligns with the PR's objectives to enhance polymorphism code generation. This change is correctly implemented.
packages/schema/src/plugins/enhancer/delegate/index.ts (1)
  • 8-9: Initializing PrismaSchemaGenerator with model directly in the constructor simplifies its usage and is consistent with the PR's objectives. This change is correctly implemented.
tests/integration/tests/enhancements/with-delegate/issue-1058.test.ts (1)
  • 3-4: The updates to the test suite and test case descriptions are appropriate and align with the PR's objectives to introduce regression tests for specific issues. The test case is correctly implemented and relevant.
tests/integration/tests/enhancements/with-delegate/issue-1064.test.ts (1)
  • 3-4: The test suite and test case for regression issue 1064 are well-structured and comprehensive, covering a wide range of models and relationships. This aligns with the PR's objectives to ensure the robustness of enhancements in handling complex schemas.
packages/schema/src/plugins/enhancer/enhance/index.ts (1)
  • 24-26: Introducing the DelegateInfo type is a good practice for encapsulating information about delegate models and their sub-models, enhancing code readability and maintainability.
packages/schema/src/plugins/prisma/schema-generator.ts (4)
  • 83-83: The introduction of PROVIDERS_SUPPORTING_NAMED_CONSTRAINTS is a good practice for maintaining a clear list of supported database providers for named constraints.
  • 100-100: The modification to the constructor of PrismaSchemaGenerator to accept a zmodel parameter simplifies its instantiation and usage, aligning with the PR's objectives.
  • 102-102: Removing the model parameter from the generate method and relying on the zmodel provided at construction simplifies the interface and usage of PrismaSchemaGenerator.
  • 484-497: The addition of the supportNamedConstraints getter is a well-implemented feature that checks for support of named constraints based on the database provider, aligning with the PR's objectives.
tests/integration/tests/enhancements/with-delegate/polymorphism.test.ts (1)
  • 2-4: The addition of imports for run, fs, and path is appropriate given their usage in the newly added TypeScript compilation test case. These modules are essential for setting up the test environment, writing the TypeScript script to a file, and executing the TypeScript compiler.

Comment on lines +1018 to +1089
it('typescript compilation', async () => {
const { projectDir } = await loadSchema(schema, { enhancements: ['delegate'] });
const src = `
import { PrismaClient } from '@prisma/client';
import { enhance } from '.zenstack/enhance';

const prisma = new PrismaClient();

async function main() {
await prisma.user.deleteMany();
const db = enhance(prisma);

const user1 = await db.user.create({ data: { } });

await db.ratedVideo.create({
data: {
owner: { connect: { id: user1.id } },
duration: 100,
url: 'abc',
rating: 10,
},
});

await db.image.create({
data: {
owner: { connect: { id: user1.id } },
format: 'webp',
},
});

const video = await db.video.findFirst({ include: { owner: true } });
console.log(video?.duration);
console.log(video?.viewCount);

const asset = await db.asset.findFirstOrThrow();
console.log(asset.assetType);
console.log(asset.viewCount);

if (asset.assetType === 'Video') {
console.log('Video: duration', asset.duration);
} else {
console.log('Image: format', asset.format);
}
}

main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
`;

fs.writeFileSync(path.join(projectDir, 'script.ts'), src);
fs.writeFileSync(
path.join(projectDir, 'tsconfig.json'),
JSON.stringify({
compilerOptions: {
outDir: 'dist',
strict: true,
lib: ['esnext'],
esModuleInterop: true,
},
})
);

run('npm i -D @types/node', undefined, projectDir);
run('npx tsc --noEmit --skipLibCheck script.ts', undefined, projectDir);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new test case for TypeScript compilation is well-structured and serves its purpose of validating the TypeScript compilation with PrismaClient operations. However, there are a few points to consider for improvement:

  1. Error Handling: The catch block in lines 1067-1071 properly logs errors and disconnects the Prisma client, which is good practice. However, it's important to ensure that any potential errors during the setup phase (e.g., writing files or installing dependencies) are also caught and handled appropriately.

  2. Dependency Installation: The command in line 1087 installs @types/node every time the test runs. Consider checking if this dependency could be included in the project's devDependencies to avoid repeated installations, which could slow down the test execution.

  3. Compiler Options: The TypeScript compiler options specified in lines 1078-1083 are focused on strict typing and modern JavaScript features. Ensure these settings align with the project's overall TypeScript configuration to maintain consistency.

  4. Script Execution: While the test verifies the TypeScript compilation (line 1088), it does not execute the compiled JavaScript. If the objective includes runtime validation, consider adding a step to execute the compiled output.

Overall, the test case is a valuable addition to the test suite, ensuring that the TypeScript code involving PrismaClient operations compiles correctly. Just ensure that the points mentioned above are considered to optimize the test execution and alignment with project standards.

- run('npm i -D @types/node', undefined, projectDir);
+ // Consider moving '@types/node' to project's devDependencies to avoid repeated installations

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
it('typescript compilation', async () => {
const { projectDir } = await loadSchema(schema, { enhancements: ['delegate'] });
const src = `
import { PrismaClient } from '@prisma/client';
import { enhance } from '.zenstack/enhance';
const prisma = new PrismaClient();
async function main() {
await prisma.user.deleteMany();
const db = enhance(prisma);
const user1 = await db.user.create({ data: { } });
await db.ratedVideo.create({
data: {
owner: { connect: { id: user1.id } },
duration: 100,
url: 'abc',
rating: 10,
},
});
await db.image.create({
data: {
owner: { connect: { id: user1.id } },
format: 'webp',
},
});
const video = await db.video.findFirst({ include: { owner: true } });
console.log(video?.duration);
console.log(video?.viewCount);
const asset = await db.asset.findFirstOrThrow();
console.log(asset.assetType);
console.log(asset.viewCount);
if (asset.assetType === 'Video') {
console.log('Video: duration', asset.duration);
} else {
console.log('Image: format', asset.format);
}
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
`;
fs.writeFileSync(path.join(projectDir, 'script.ts'), src);
fs.writeFileSync(
path.join(projectDir, 'tsconfig.json'),
JSON.stringify({
compilerOptions: {
outDir: 'dist',
strict: true,
lib: ['esnext'],
esModuleInterop: true,
},
})
);
run('npm i -D @types/node', undefined, projectDir);
run('npx tsc --noEmit --skipLibCheck script.ts', undefined, projectDir);
});
it('typescript compilation', async () => {
const { projectDir } = await loadSchema(schema, { enhancements: ['delegate'] });
const src = `
import { PrismaClient } from '@prisma/client';
import { enhance } from '.zenstack/enhance';
const prisma = new PrismaClient();
async function main() {
await prisma.user.deleteMany();
const db = enhance(prisma);
const user1 = await db.user.create({ data: { } });
await db.ratedVideo.create({
data: {
owner: { connect: { id: user1.id } },
duration: 100,
url: 'abc',
rating: 10,
},
});
await db.image.create({
data: {
owner: { connect: { id: user1.id } },
format: 'webp',
},
});
const video = await db.video.findFirst({ include: { owner: true } });
console.log(video?.duration);
console.log(video?.viewCount);
const asset = await db.asset.findFirstOrThrow();
console.log(asset.assetType);
console.log(asset.viewCount);
if (asset.assetType === 'Video') {
console.log('Video: duration', asset.duration);
} else {
console.log('Image: format', asset.format);
}
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
`;
fs.writeFileSync(path.join(projectDir, 'script.ts'), src);
fs.writeFileSync(
path.join(projectDir, 'tsconfig.json'),
JSON.stringify({
compilerOptions: {
outDir: 'dist',
strict: true,
lib: ['esnext'],
esModuleInterop: true,
},
})
);
// Consider moving '@types/node' to project's devDependencies to avoid repeated installations
run('npx tsc --noEmit --skipLibCheck script.ts', undefined, projectDir);
});

Comment on lines 17 to 23
InvocationExpr,
isArrayExpr,
isDataModel,
isDataSource,
isInvocationExpr,
isLiteralExpr,
isNullExpr,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 NOTE
This review was outside the diff hunks, and no overlapping diff hunk was found. Original lines [707-707]

Potential security issue detected: user input is used in path.resolve within the getDefaultPrismaOutputFile function, which could lead to a path traversal vulnerability. Ensure to sanitize or validate user input before using it in path resolution functions.

- return path.resolve(path.dirname(pkgJsonPath), pkgJson.zenstack.prisma);
+ // Ensure pkgJson.zenstack.prisma is sanitized or validated before use
+ return path.resolve(path.dirname(pkgJsonPath), sanitizedInput);

Comment on lines 5 to 12
import path from 'path';

export async function generate(model: Model, options: PluginOptions, project: Project, outDir: string) {
const prismaGenerator = new PrismaSchemaGenerator();
await prismaGenerator.generate(model, {
const prismaGenerator = new PrismaSchemaGenerator(model);
await prismaGenerator.generate({
provider: '@internal',
schemaPath: options.schemaPath,
output: path.join(outDir, 'delegate.prisma'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 NOTE
This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [12-13]

The use of path.join with options.schemaPath and outDir could potentially lead to path traversal vulnerabilities if user input is not properly sanitized or validated. Ensure that any user-supplied paths are rigorously checked to prevent unauthorized file system access.

- output: path.join(outDir, 'delegate.prisma'),
- overrideClientGenerationPath: path.join(outDir, '.delegate'),
+ output: sanitizePath(path.join(outDir, 'delegate.prisma')),
+ overrideClientGenerationPath: sanitizePath(path.join(outDir, '.delegate')),

Consider implementing a sanitizePath function that checks for and mitigates any path traversal patterns.

} from 'ts-morph';
import { PrismaSchemaGenerator } from '../../prisma/schema-generator';

// information of delegate models and their sub models
type DelegateInfo = [DataModel, DataModel[]][];

export async function generate(model: Model, options: PluginOptions, project: Project, outDir: string) {
const outFile = path.join(outDir, 'enhance.ts');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of path.join with outDir could potentially lead to path traversal vulnerabilities if user input is not properly sanitized or validated. Ensure that any user-supplied paths are rigorously checked to prevent unauthorized file system access.

- const outFile = path.join(outDir, 'enhance.ts');
+ const outFile = sanitizePath(path.join(outDir, 'enhance.ts'));

Consider implementing a sanitizePath function that checks for and mitigates any path traversal patterns.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const outFile = path.join(outDir, 'enhance.ts');
const outFile = sanitizePath(path.join(outDir, 'enhance.ts'));

Comment on lines +264 to +266
const delegateCreateUpdateInputRegex = new RegExp(
`\\${delegateModelNames.join('|')}(Unchecked)?(Create|Update).*Input`
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dynamic construction of a regular expression from user input or variable names could potentially lead to a Regular Expression Denial-of-Service (ReDoS) vulnerability. Consider using hardcoded regexes or performing input validation to mitigate this risk.

Consider refactoring to use hardcoded regexes or adding input validation to ensure the regex does not become a vector for ReDoS.

Comment on lines +287 to +289
const concreteCreateUpdateInputRegex = new RegExp(
`(${concreteModelNames.join('|')})(Unchecked)?(Create|Update).*Input`
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the previous comment, dynamically constructing a regular expression from user input or variable names could lead to a Regular Expression Denial-of-Service (ReDoS) vulnerability. It's important to use hardcoded regexes or perform input validation to mitigate this risk.

Consider refactoring to use hardcoded regexes or adding input validation to ensure the regex does not become a vector for ReDoS.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 64a52dc and 03957f4.
Files selected for processing (1)
  • packages/schema/tests/generator/prisma-generator.test.ts (13 hunks)
Additional comments: 1
packages/schema/tests/generator/prisma-generator.test.ts (1)
  • 52-52: The modification to pass model as a parameter to PrismaSchemaGenerator's generate method instead of creating a new instance each time is a significant improvement in terms of efficiency. This change allows for the reuse of the PrismaSchemaGenerator instance across multiple calls to generate, reducing the overhead of instance creation and potentially improving performance, especially in scenarios where generate is called multiple times.

@ymc9 ymc9 merged commit 5b103ba into v2 Mar 4, 2024
12 checks passed
@ymc9 ymc9 deleted the perf/delegate-generate-perf branch March 4, 2024 02:16
@github-actions github-actions bot mentioned this pull request Mar 2, 2024
@ymc9
Copy link
Member Author

ymc9 commented Mar 4, 2024

Fixes #1064

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant