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

feat : adds pagination #1636

Merged
merged 5 commits into from
Jan 12, 2025
Merged

feat : adds pagination #1636

merged 5 commits into from
Jan 12, 2025

Conversation

rajadilipkolli
Copy link
Owner

@rajadilipkolli rajadilipkolli commented Jan 11, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced routing with media type predicates for more precise request handling.
    • Improved data retrieval efficiency for posts and comments.
    • Introduced pagination support for retrieving posts.
    • Added a structured way to represent paginated results.
    • Created a new ENUM type for post status in the database schema.
  • Improvements

    • Added code coverage and test reporting configurations.
    • Implemented automatic timestamp and versioning for posts.
    • Updated database schema to support more flexible text content.
  • Bug Fixes

    • Improved error handling in post update process.
    • Enhanced resource management for JSON parsing.
  • Chores

    • Updated logging levels for reduced verbosity.
    • Added Git commit information generation.
    • Introduced integration tests for post handling functionality.
    • Updated Docker configurations for service and resource management.

@rajadilipkolli rajadilipkolli self-assigned this Jan 11, 2025
Copy link
Contributor

coderabbitai bot commented Jan 11, 2025

Walkthrough

This pull request introduces significant enhancements to a Spring Boot R2DBC project, focusing on testing configurations, data handling, and application structure. Key changes include the addition of JaCoCo for code coverage, modifications to the logging levels, improvements in pagination and error handling in data retrieval, and updates to the database schema. The integration tests have been expanded to validate new functionalities, ensuring robust testing practices throughout the application.

Changes

File Change Summary
pom.xml Added JaCoCo and Git Commit ID plugins; removed test skipping properties; introduced new test reporting and coverage configurations.
PgJsonObjectJsonComponent.java Reduced logging verbosity; improved resource management for JSON parsing.
WebRouterConfig.java Updated routing configuration with media type predicates; changed method visibility from public to package-private.
Post.java Added annotations for created/modified dates, versioning, and transient field handling.
PostHandler.java Optimized post retrieval with pagination; improved error handling in update method.
CommentRepository.java Added method to retrieve comments for multiple post IDs.
V1__01_schema.sql Changed column types to TEXT; added NOT NULL constraints and created a new ENUM type for post status.
PagedResult.java Introduced a new record for encapsulating pagination information.
PostRepository.java Removed ReactiveQueryByExampleExecutor; added method for retrieving posts with pagination.
ApplicationIntTest.java Added integration test for loading posts.
PostHandlerIntTest.java Introduced integration tests for PostHandler functionality with pagination scenarios.
Dockerfile Introduced a multi-stage build process for optimizing the Spring Boot application image.
docker-compose-app.yml Updated service name and added resource limits and health check configurations.

Sequence Diagram

sequenceDiagram
    participant Client
    participant WebRouter
    participant PostHandler
    participant PostRepository
    participant CommentRepository

    Client->>WebRouter: Request posts
    WebRouter->>PostHandler: Route request
    PostHandler->>PostRepository: Fetch posts with pagination
    PostRepository-->>PostHandler: Return paginated posts
    PostHandler->>CommentRepository: Fetch comments by post IDs
    CommentRepository-->>PostHandler: Return comments
    PostHandler->>Client: Response with posts and comments
Loading

Possibly related PRs

Poem

🐰 A Rabbit's Ode to Code Refinement 🔧
In pom.xml, tests dance with grace,
Coverage reports now find their place.
Entities bloom with annotations bright,
Routing predicates set paths just right.
Code evolves, a technological delight!
hop hop 🥕


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0a5015 and 1c2bc58.

📒 Files selected for processing (3)
  • r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (3 hunks)
  • r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/ApplicationIntTest.java (1 hunks)
  • r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/handler/PostHandlerIntTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/ApplicationIntTest.java
  • r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java
  • r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/handler/PostHandlerIntTest.java
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Codacy Static Code Analysis

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:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • 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 testing code 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 gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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 using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • 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/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added type: dependency-upgrade Pull requests that update a dependency file component: r2dbc labels Jan 11, 2025
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.

Actionable comments posted: 2

🧹 Nitpick comments (8)
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/repository/CommentRepository.java (1)

13-13: Well-designed method for batch comment retrieval!

The method follows Spring Data naming conventions and appropriately uses reactive types (Flux) for R2DBC operations. This is a good optimization that will help reduce N+1 query problems when fetching comments for multiple posts.

For optimal performance with this method:

  • Consider adding an index on the post_id column if not already present
  • Be mindful of the size of the postIds list to avoid excessive memory usage or query complexity
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/config/PgJsonObjectJsonComponent.java (1)

34-39: Good improvements, with a suggestion for optimization

The changes look good:

  1. Using debug level for JSON payload logging is appropriate
  2. Using try-with-resources ensures proper cleanup of the JsonParser

However, there's room for optimization:

Consider making JsonFactory a static final field since it's thread-safe and reusable:

static class Serializer extends JsonSerializer<Json> {
+    private static final JsonFactory FACTORY = new JsonFactory();

    @Override
    public void serialize(Json value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        var text = value.asString();
        log.debug("The raw json value from PostgresSQL JSON type:{}", text);
-       JsonFactory factory = new JsonFactory();
-       try (JsonParser parser = factory.createParser(text)) {
+       try (JsonParser parser = FACTORY.createParser(text)) {
            var node = gen.getCodec().readTree(parser);
            serializers.defaultSerializeValue(node, gen);
        }
    }
}
r2dbc/boot-r2dbc-json-column/src/main/resources/db/migration/postgresql/V1__01_schema.sql (2)

8-9: Good choice of data types, but consider performance implications.

The switch from VARCHAR(255) to TEXT is a good choice for flexible content storage. However, consider the following recommendations:

  1. For the title column, you might want to add a CHECK constraint to prevent extremely long titles that could affect UI rendering:
title TEXT NOT NULL CHECK (length(title) <= 500)
  1. Consider adding an index on title if you perform searches on this column, as TEXT fields benefit from specialized indexes like GiST or GIN for full-text search.

Also applies to: 11-11


21-21: Consider adding content length validation.

While TEXT is appropriate for comment content, consider adding a reasonable maximum length constraint to prevent potential abuse:

content TEXT NOT NULL CHECK (length(content) <= 5000)

This helps prevent spam and ensures database performance remains predictable.

r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/config/WebRouterConfig.java (1)

22-39: Consider adding OpenAPI documentation

While the routes are well-structured, consider enhancing them with OpenAPI documentation using SpringDoc annotations. This would improve API discoverability and make the API contract more explicit.

Example enhancement:

@OpenAPIDefinition(
    info = @Info(
        title = "Posts and Comments API",
        version = "1.0",
        description = "API for managing posts and comments"
    )
)
r2dbc/boot-r2dbc-json-column/pom.xml (1)

199-241: Consider test execution order strategy.

While alphabetical ordering (runOrder=alphabetical) provides consistency, it might not be optimal for:

  • Test independence (tests might inadvertently depend on alphabetical order)
  • Early failure detection (critical tests might run later)

Consider using filesystem (default) or hourly ordering instead, which can help identify timing-dependent test issues.

r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (2)

41-42: Remove unnecessary casting to (List<Comment>)

The cast to (List<Comment>) is unnecessary because commentsByPost.getOrDefault(post.getId(), List.of()) already returns a Collection<Comment>, and List.of() returns an immutable list. If the setComments method accepts a Collection<Comment>, you can remove the cast.

Apply this diff to remove the unnecessary cast:

- posts.forEach(post -> post.setComments(
-         (List<Comment>) commentsByPost.getOrDefault(post.getId(), List.of())));
+ posts.forEach(post -> post.setComments(
+         commentsByPost.getOrDefault(post.getId(), List.of())));

63-76: Enhance error handling in the update method

Currently, the onErrorResume handles only IllegalArgumentException. Other exceptions, such as database errors or validation exceptions, might not be properly handled and could result in exposing server errors to the client. Consider adding a generic error handler or handling specific exceptions that might occur during the update process to ensure robust error management.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8cd5dd1 and 3f43d0f.

📒 Files selected for processing (7)
  • r2dbc/boot-r2dbc-json-column/pom.xml (2 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/config/PgJsonObjectJsonComponent.java (2 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/config/WebRouterConfig.java (2 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/entity/Post.java (2 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (3 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/repository/CommentRepository.java (1 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/resources/db/migration/postgresql/V1__01_schema.sql (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Run Unit & Integration Tests
🔇 Additional comments (14)
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/repository/CommentRepository.java (1)

4-4: LGTM!

The List import is correctly added to support the new method parameter.

r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/config/PgJsonObjectJsonComponent.java (1)

24-24: Good improvement on log level!

Changing to debug level is more appropriate for JSON payload logging and helps reduce log verbosity in production.

r2dbc/boot-r2dbc-json-column/src/main/resources/db/migration/postgresql/V1__01_schema.sql (1)

Line range hint 1-28: Well-structured schema with good practices!

The schema demonstrates several good practices:

  • Proper use of UUID extension
  • Good referential integrity with CASCADE delete
  • Audit fields (created_at, updated_at)
  • Optimistic locking support (version column)
  • JSON support for flexible metadata
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/config/WebRouterConfig.java (3)

3-5: LGTM! Good optimization with proxyBeanMethods=false

The addition of static imports for media type predicates and the optimization of bean creation by disabling CGLIB proxying are good improvements. This is particularly beneficial since this configuration class doesn't require proxying for method interception.

Also applies to: 16-16


20-21: LGTM! Good encapsulation with package-private visibility

Reducing the method visibility to package-private is a good practice for encapsulation since this bean is only used within the Spring context.


22-26: LGTM! Well-structured content negotiation

The addition of explicit media type predicates for both request and response improves the API contract by ensuring proper content negotiation. The consistent use of APPLICATION_JSON across all routes maintains uniformity.

Also applies to: 28-32, 34-39

r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/entity/Post.java (4)

9-13: LGTM! Clean and specific imports.

The imports are well-organized and follow best practices by using specific imports rather than wildcards.


53-55: Enhance documentation for manual comment population.

While the @transient and @Builder.Default annotations are correctly applied, consider adding more detailed documentation about:

  1. When and how comments are populated
  2. The relationship with Comment entity
  3. Performance implications of manual population

Replace the current comment with more detailed documentation:

-    private List<Comment> comments = new ArrayList<>(); // This field is transient and will be populated manually
+    private List<Comment> comments = new ArrayList<>(); // Transient field populated manually via CommentRepository.findAllByPostIdIn()
+    // for efficient batch loading of comments. Not persisted in the posts table.

Let's verify the comment population implementation:

✅ Verification successful

Documentation suggestion aligns with implementation

The review comment's documentation suggestion is accurate but can be even more precise based on the actual implementation:

-    private List<Comment> comments = new ArrayList<>(); // This field is transient and will be populated manually
+    private List<Comment> comments = new ArrayList<>(); // Transient field batch-loaded via CommentRepository.findAllByPostIdIn()
+    // and grouped by postId. Not persisted in posts table to maintain separation of concerns.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for comment population logic
rg -A 5 "findAllByPostIdIn|populateComments" --type java

Length of output: 1254


Line range hint 40-45: Verify Spring Data auditing configuration.

The auditing annotations (@CreatedDate, @LastModifiedDate) are correctly applied, but ensure that auditing is enabled in your configuration.

✅ Verification successful

Auditing is properly configured ✓

The @EnableR2dbcAuditing annotation is correctly configured in ReactiveDataConfig.java along with the necessary ReactiveAuditorAware bean, which will ensure that your @CreatedDate and @LastModifiedDate annotations work as expected.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for auditing configuration
rg -l "EnableR2dbcAuditing|AbstractR2dbcConfiguration" --type java

Length of output: 355


Script:

#!/bin/bash
# Check content of the configuration file
cat "r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/config/ReactiveDataConfig.java"

Length of output: 623


Line range hint 47-49: Verify schema support for optimistic locking.

The @Version annotation is correctly applied for optimistic locking. Ensure that the database schema includes the version column with appropriate constraints.

✅ Verification successful

Schema correctly supports optimistic locking

The database schema includes a version column of type INTEGER with appropriate NOT NULL constraint and default value of 0, which properly supports the @Version annotation for optimistic locking.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for version column in migration scripts
rg -l "version\s+[bB]igint|version.*serial" --type sql

Length of output: 56


Script:

#!/bin/bash
# First, find all potential schema files
echo "=== Finding schema files ==="
fd -e sql -e ddl -e schema

echo "=== Searching for version column with various patterns ==="
rg -i "create.*table.*post.*version|alter.*table.*post.*version" --type-add 'schema:*.{sql,ddl,schema}' --type schema -A 5 -B 5

Length of output: 2925


Script:

#!/bin/bash
# Check the content of the schema file
cat r2dbc/boot-r2dbc-json-column/src/main/resources/db/migration/postgresql/V1__01_schema.sql

Length of output: 1021

r2dbc/boot-r2dbc-json-column/pom.xml (3)

20-30: Well-structured test properties configuration!

Good choice of 80% as the minimum coverage threshold - this is a widely accepted industry standard that balances code quality with maintainability.


177-198: Security-conscious Git information exposure!

Good practice to:

  • Limit exposed Git properties to essential information only
  • Handle missing Git directory gracefully
  • Generate properties file for runtime access

Line range hint 5-9: Verify Spring Boot version choice.

Using Spring Boot 3.4.1 (milestone version) in production could be risky as it might contain breaking changes or undiscovered issues. Consider using the latest stable version unless there's a specific requirement for this milestone version.

Run this script to check the latest stable version:

r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (1)

63-71: Simplify the property update logic

Manually setting each property can be error-prone and hard to maintain if the Post entity gains more fields in the future. Consider using a utility method or a mapper to copy properties from updatedPost to existingPost, excluding immutable or identifier fields.

For example, using Spring's BeanUtils:

existingPost -> req.bodyToMono(Post.class).map(updatedPost -> {
-    existingPost.setTitle(updatedPost.getTitle());
-    existingPost.setContent(updatedPost.getContent());
-    existingPost.setMetadata(updatedPost.getMetadata());
-    existingPost.setStatus(updatedPost.getStatus());
+    BeanUtils.copyProperties(updatedPost, existingPost, "id", "comments");
     return existingPost;
})

Ensure that the copyProperties method correctly handles all necessary fields and that you exclude any fields that should not be overwritten.

Comment on lines +242 to +315
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-maven-plugin.version}</version>
<configuration>
<dataFile>${project.build.directory}/jacoco/test/jacoco.exec</dataFile>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>COMPLEXITY</counter>
<value>COVEREDRATIO</value>
<minimum>${jacoco.minimum.coverage}</minimum>
</limit>
</limits>
</rule>
</rules>
<excludes>
<exclude>**/*Application.*</exclude>
<exclude>**/config/**</exclude>
<exclude>**/models/*</exclude>
<exclude>**/dtos/*</exclude>
<exclude>**/exceptions/*</exclude>
<exclude>**/*Constants*</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>pre-unit-tests</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<!-- Sets the path to the file which contains the execution data. -->
<destFile>${jacoco.utReportFile}</destFile>
</configuration>
</execution>
<!-- Ensures that the code coverage report for unit tests is created after unit tests have been run -->
<execution>
<id>post-unit-test</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>${jacoco.utReportFile}</dataFile>
<outputDirectory>${jacoco.utReportFolder}</outputDirectory>
</configuration>
</execution>
<execution>
<id>pre-integration-tests</id>
<goals>
<goal>prepare-agent-integration</goal>
</goals>
<configuration>
<!-- Sets the path to the file which contains the execution data. -->
<destFile>${jacoco.itReportFile}</destFile>
</configuration>
</execution>
<!-- Ensures that the code coverage report for integration tests is created after integration tests have been run -->
<execution>
<id>post-integration-tests</id>
<phase>post-integration-test</phase>
<goals>
<goal>report-integration</goal>
</goals>
<configuration>
<dataFile>${jacoco.itReportFile}</dataFile>
<outputDirectory>${jacoco.itReportFolder}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Maintain test coverage for business-critical components

The current broad exclusion patterns should be refined as the excluded components contain important business logic:

  • DTOs contain core business objects and validations (e.g., CustomerDTO, Orders)
  • Configuration classes implement critical security rules and bean configurations
  • Exception handlers contain custom business error handling logic

Recommended approach:

  • Only exclude application bootstrap classes
  • Keep coverage for DTOs, models, and exceptions containing business logic
  • Maintain tests for configuration classes implementing business rules
🔗 Analysis chain

Review coverage exclusion patterns.

The current exclusion patterns might be too broad:

<excludes>
    <exclude>**/*Application.*</exclude>
    <exclude>**/config/**</exclude>
    <exclude>**/models/*</exclude>
    <exclude>**/dtos/*</exclude>
    <exclude>**/exceptions/*</exclude>
    <exclude>**/*Constants*</exclude>
</excludes>

While it's common to exclude application bootstrap classes, consider:

  • Models and DTOs might benefit from coverage to ensure proper initialization and validation
  • Exception classes might contain business logic that should be tested
  • Configuration classes might contain crucial business rules

Would you like me to help create a more granular exclusion pattern that better balances coverage with maintainability?

🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Check for business logic in models, DTOs, and exceptions
echo "=== Checking models for methods ==="
ast-grep --pattern 'class $_ {
  $$$
  $METHODNAME($$$) {
    $$$
  }
  $$$
}'  | rg "models/"

echo -e "\n=== Checking DTOs for methods ==="
ast-grep --pattern 'class $_ {
  $$$
  $METHODNAME($$$) {
    $$$
  }
  $$$
}'  | rg "dtos/"

echo -e "\n=== Checking exceptions for custom logic ==="
ast-grep --pattern 'class $_ extends $_ {
  $$$
  $METHODNAME($$$) {
    $$$
  }
  $$$
}'  | rg "exceptions/"

echo -e "\n=== Checking config classes for business rules ==="
ast-grep --pattern 'class $_ {
  $$$
  @Bean
  $METHODNAME($$$) {
    $$$
  }
  $$$
}'  | rg "config/"

echo -e "\n=== Looking for existing tests ==="
fd -e java -e kt --full-path "(Test|IT|Spec)\\.java$|Test\\.kt$" | rg "(models|dtos|exceptions|config)"

Length of output: 971


Script:

#!/bin/bash
# First check if these directories exist and contain any code
echo "=== Checking directory structure ==="
fd -t d "(models|dtos|config|exceptions)"

echo -e "\n=== Looking for Java/Kotlin files in these directories ==="
fd -e java -e kt | rg "(models|dtos|config|exceptions)"

echo -e "\n=== Examining content of any found files ==="
fd -e java -e kt | rg "(models|dtos|config|exceptions)" | xargs -I {} sh -c 'echo "=== {} ==="; cat "{}" 2>/dev/null || true'

Length of output: 73543

@rajadilipkolli rajadilipkolli changed the title feat : polish json column implementation feat : adds pagination Jan 12, 2025
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.

Actionable comments posted: 7

🧹 Nitpick comments (10)
r2dbc/boot-r2dbc-json-column/src/main/resources/db/migration/postgresql/V1__01_schema.sql (4)

8-10: Enhance title validation constraints

While the LENGTH check is good, consider adding a constraint to prevent whitespace-only values based on previous successful implementations.

-            title TEXT NOT NULL CHECK(
-                LENGTH(title)<= 500
-            ),
+            title TEXT NOT NULL CHECK(
+                LENGTH(TRIM(title)) > 0 AND
+                LENGTH(title) <= 500
+            ),

11-11: Add validation for content column

Consider adding the same whitespace validation for the content column to maintain data quality.

-            content TEXT NOT NULL,
+            content TEXT NOT NULL CHECK(LENGTH(TRIM(content)) > 0),

13-13: Consider using ENUM type for status

Using TEXT for status values allows any string, which could lead to invalid states. Consider using PostgreSQL's ENUM type for better type safety.

-            status TEXT DEFAULT 'DRAFT',
+            status post_status DEFAULT 'DRAFT',

Add before the table creation:

CREATE TYPE post_status AS ENUM ('DRAFT', 'PUBLISHED', 'ARCHIVED');

23-23: Add validation for comment content

Apply the same whitespace validation pattern to maintain consistency across content columns.

-            content TEXT NOT NULL,
+            content TEXT NOT NULL CHECK(LENGTH(TRIM(content)) > 0),
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (2)

35-58: Robust pagination implementation with efficient comment fetching.

The implementation follows best practices:

  1. Proper parameter extraction with defaults
  2. Efficient database-level pagination
  3. Batch fetching of comments for paginated posts
  4. Structured response using PagedResult

Consider adding parameter validation for negative values.

Add parameter validation:

 public Mono<ServerResponse> all(ServerRequest req) {
     int page = Integer.parseInt(req.queryParam("page").orElse("0"));
     int size = Integer.parseInt(req.queryParam("size").orElse("10"));
+    if (page < 0 || size <= 0) {
+        return ServerResponse.badRequest()
+            .bodyValue("Page must be >= 0 and size must be > 0");
+    }
     return this.postRepository

75-88: Improved error handling with clear status codes.

Good improvements:

  1. Clear 404 handling for missing posts
  2. Proper 400 handling for invalid requests
  3. Efficient control flow using switchIfEmpty

Consider adding logging for error scenarios.

Add error logging:

 public Mono<ServerResponse> update(ServerRequest req) {
     return this.postRepository
             .findById(UUID.fromString(req.pathVariable("id")))
-            .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, "Post not found")))
+            .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, 
+                    String.format("Post not found with id: %s", req.pathVariable("id")))))
             .flatMap(existingPost -> req.bodyToMono(Post.class).map(updatedPost -> {
                 existingPost.setTitle(updatedPost.getTitle());
                 existingPost.setContent(updatedPost.getContent());
                 existingPost.setMetadata(updatedPost.getMetadata());
                 existingPost.setStatus(updatedPost.getStatus());
                 return existingPost;
             }))
             .flatMap(this.postRepository::save)
             .flatMap(post -> noContent().build())
             .onErrorResume(IllegalArgumentException.class, e -> {
+                log.error("Error updating post: {}", e.getMessage());
                 return ServerResponse.badRequest()
                     .bodyValue(e.getMessage());
             });
 }
r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/ApplicationIntTest.java (3)

14-41: Consider adding edge cases and error scenarios.

The test only covers the happy path. Consider adding tests for:

  • Empty result set
  • Multiple pages
  • Invalid page parameters
  • Error responses

24-32: Consider making the test data expectations more maintainable.

The test has hardcoded expectations (e.g., totalElements=2, hasSize(2)) which could make it brittle. Consider:

  1. Using test data builders or factories
  2. Loading test data from fixtures
  3. Making the expectations relative to the test data setup

35-37: Enhance content validation.

The test only verifies that fields are not null. Consider adding assertions for:

  1. Actual content values
  2. Data types and formats
  3. Relationships between fields
r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/handler/PostHandlerIntTest.java (1)

23-34: Consider non-blocking setup and data verification

The setup method has a few areas for improvement:

  1. Using blockLast() is blocking and could impact test performance
  2. Test data is very basic and might not catch edge cases
  3. No verification that setup completed successfully

Consider this alternative implementation:

 @BeforeEach
 void setUp() {
     // Create test data
     var posts = IntStream.range(1, 21) // Create 20 posts
             .mapToObj(i -> Post.builder()
                     .title("Test Post " + i)
-                    .content("Content " + i)
+                    .content("Detailed content with special characters: !@#$ " + i)
                     .build())
             .collect(Collectors.toList());

     // Clear existing data and save new test data
-    postRepository.deleteAll().thenMany(postRepository.saveAll(posts)).blockLast();
+    StepVerifier.create(postRepository.deleteAll()
+            .thenMany(postRepository.saveAll(posts))
+            .collectList())
+            .expectNextCount(1)
+            .verifyComplete();
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f43d0f and fa0b42b.

📒 Files selected for processing (6)
  • r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (3 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/model/response/PagedResult.java (1 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/repository/PostRepository.java (1 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/resources/db/migration/postgresql/V1__01_schema.sql (2 hunks)
  • r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/ApplicationIntTest.java (1 hunks)
  • r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/handler/PostHandlerIntTest.java (1 hunks)
🧰 Additional context used
📓 Learnings (4)
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/repository/PostRepository.java (2)
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-11T14:14:45.469Z
Learning: The pagination implementation in Spring WebFlux R2DBC applications should include:
1. Page and size parameters in the request
2. Repository method with Pageable parameter
3. Response with both data and pagination metadata (pageNumber, pageSize, totalElements, totalPages)
4. Efficient fetching of related entities for the paginated results
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-12T05:27:36.588Z
Learning: In Spring WebFlux R2DBC applications, proper database-level pagination should use native SQL query with LIMIT and OFFSET clauses, as JPQL-style queries don't translate to efficient SQL pagination. The offset should be calculated as page * size.
r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/handler/PostHandlerIntTest.java (1)
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-12T05:53:38.355Z
Learning: Integration tests for paginated endpoints in Spring WebFlux should verify:
1. Default pagination behavior
2. Custom page size and number
3. Response structure including metadata (totalElements, totalPages)
4. Edge cases like out-of-bounds pages
5. Data consistency across pages
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (3)
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-12T05:53:38.355Z
Learning: Integration tests for paginated endpoints in Spring WebFlux should verify:
1. Default pagination behavior
2. Custom page size and number
3. Response structure including metadata (totalElements, totalPages)
4. Edge cases like out-of-bounds pages
5. Data consistency across pages
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-12T05:27:36.588Z
Learning: In Spring WebFlux R2DBC applications, proper database-level pagination should use native SQL query with LIMIT and OFFSET clauses, as JPQL-style queries don't translate to efficient SQL pagination. The offset should be calculated as page * size.
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-11T14:14:45.469Z
Learning: The pagination implementation in Spring WebFlux R2DBC applications should include:
1. Page and size parameters in the request
2. Repository method with Pageable parameter
3. Response with both data and pagination metadata (pageNumber, pageSize, totalElements, totalPages)
4. Efficient fetching of related entities for the paginated results
r2dbc/boot-r2dbc-json-column/src/main/resources/db/migration/postgresql/V1__01_schema.sql (1)
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1632
File: r2dbc/boot-jooq-r2dbc-sample/src/main/resources/db/migration/postgresql/V1__01_init.sql:8-8
Timestamp: 2025-01-10T06:17:49.965Z
Learning: In PostgreSQL, to prevent empty strings or whitespace-only values in text columns, use CHECK constraints with LENGTH(TRIM(column_name)) > 0. This is more robust than just NOT NULL constraint which allows empty strings.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (5)
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/repository/PostRepository.java (2)

12-12: LGTM! Simplified interface inheritance.

The removal of ReactiveQueryByExampleExecutor streamlines the interface by removing unused functionality.


21-22: Efficient database-level pagination implementation.

The native SQL query with LIMIT and OFFSET ensures proper database-level pagination, improving performance for large datasets.

Let's verify the pagination query execution:

✅ Verification successful

Pagination implementation verified as efficient and unique

The implementation is the only pagination query in the codebase and follows best practices for R2DBC database-level pagination.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any other pagination implementations that might conflict
rg -A 2 "LIMIT.*OFFSET" --type java

Length of output: 502

r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/model/response/PagedResult.java (2)

7-15: Well-structured pagination response model.

The record provides a clean, immutable structure with all necessary pagination metadata. Good use of @JsonProperty annotations for boolean fields.


16-26: LGTM! Proper page number handling.

The constructor correctly converts Spring's 0-based page numbers to 1-based numbers in the response, which is more intuitive for API consumers.

r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/ApplicationIntTest.java (1)

1-12: LGTM! Clean class structure with focused imports.

The test class is well-organized and properly extends the base integration test class.

import org.junit.jupiter.api.Test;
import org.springframework.core.ParameterizedTypeReference;

class ApplicationIntTest extends AbstractIntegrationTest {
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 12, 2025

Choose a reason for hiding this comment

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

💡 Codebase verification

Add tests for uncovered repository methods

The base test configuration is correct, but test coverage is incomplete:

  • Missing tests for title search functionality (findByTitleContains, findByTitleLike)
  • Missing tests for pagination methods with custom parameters
  • Missing tests for comment-related operations (findByPostId, findAllByPostIdIn)
🔗 Analysis chain

Verify test coverage and base class configuration.

Let's verify the test setup and coverage:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if AbstractIntegrationTest is properly configured
ast-grep --pattern 'class AbstractIntegrationTest {
  $$$
  @Autowired
  protected WebTestClient webTestClient;
  $$$
}'

# Check for other repository methods that might need testing
rg -t java "interface.*Repository.*extends.*R2dbcRepository" -A 10

Length of output: 3245


Script:

#!/bin/bash
# Find AbstractIntegrationTest implementation
fd AbstractIntegrationTest --exec cat {}

# Check ApplicationIntTest implementation
fd ApplicationIntTest --exec cat {}

Length of output: 33375

Copy link
Owner Author

Choose a reason for hiding this comment

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

@coderabbitai, please help me to write integration tests for Missing tests for title search functionality (findByTitleContains, findByTitleLike) and Missing tests for comment-related operations (findByPostId, findAllByPostIdIn)

Copy link
Contributor

Choose a reason for hiding this comment

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

@rajadilipkolli Here are the integration tests for title search and comment operations:

@Test
void willLoadPostsByTitleSearch() {
    webTestClient.get()
            .uri("/posts?title=Post")
            .accept(MediaType.APPLICATION_JSON)
            .exchange()
            .expectStatus().isOk()
            .expectBody(new ParameterizedTypeReference<PagedResult<Post>>() {})
            .value(response -> {
                assertThat(response).isNotNull();
                assertThat(response.data()).isNotNull()
                    .isNotEmpty()
                    .hasSize(2);
                assertThat(response.totalElements()).isEqualTo(2);
                Post firstPost = response.data().get(0);
                assertThat(firstPost.getTitle()).contains("Post");
            });
}

@Test
void willLoadPostsByTitlePattern() {
    webTestClient.get()
            .uri("/posts?title=Post%1") // Using wildcard pattern
            .accept(MediaType.APPLICATION_JSON)
            .exchange()
            .expectStatus().isOk()
            .expectBody(new ParameterizedTypeReference<PagedResult<Post>>() {})
            .value(response -> {
                assertThat(response).isNotNull();
                assertThat(response.data()).isNotNull()
                    .isNotEmpty()
                    .hasSize(1);
                Post matchedPost = response.data().get(0);
                assertThat(matchedPost.getTitle()).isEqualTo("Post1");
            });
}

@Test
void willLoadCommentsByPostId() {
    // First get a post ID
    webTestClient.get()
            .uri("/posts")
            .accept(MediaType.APPLICATION_JSON)
            .exchange()
            .expectStatus().isOk()
            .expectBody(new ParameterizedTypeReference<PagedResult<Post>>() {})
            .value(response -> {
                Post firstPost = response.data().get(0);
                UUID postId = firstPost.getId();
                
                // Then fetch comments for that post
                webTestClient.get()
                        .uri("/posts/" + postId + "/comments")
                        .accept(MediaType.APPLICATION_JSON)
                        .exchange()
                        .expectStatus().isOk()
                        .expectBodyList(Comment.class)
                        .value(comments -> {
                            assertThat(comments).isNotNull()
                                .isNotEmpty()
                                .hasSize(4);
                            Comment firstComment = comments.get(0);
                            assertThat(firstComment.getPostId()).isEqualTo(postId);
                        });
            });
}

@Test
void willLoadCommentsByMultiplePostIds() {
    // First get multiple post IDs
    webTestClient.get()
            .uri("/posts?size=2")
            .accept(MediaType.APPLICATION_JSON)
            .exchange()
            .expectStatus().isOk()
            .expectBody(new ParameterizedTypeReference<PagedResult<Post>>() {})
            .value(response -> {
                List<UUID> postIds = response.data().stream()
                    .map(Post::getId)
                    .toList();
                
                // Then fetch comments for those posts
                webTestClient.get()
                        .uri("/posts/comments?postIds=" + String.join(",", postIds.stream().map(UUID::toString).toList()))
                        .accept(MediaType.APPLICATION_JSON)
                        .exchange()
                        .expectStatus().isOk()
                        .expectBodyList(Comment.class)
                        .value(comments -> {
                            assertThat(comments).isNotNull()
                                .isNotEmpty()
                                .hasSize(8); // 4 comments per post
                            assertThat(comments.stream()
                                .map(Comment::getPostId)
                                .distinct()
                                .count()).isEqualTo(2); // Comments belong to 2 different posts
                        });
            });
}

These tests cover:

  1. Searching posts by title using exact match
  2. Searching posts by title using pattern matching
  3. Fetching comments for a single post
  4. Fetching comments for multiple posts

Each test includes assertions to verify:

  • Response status
  • Data presence and size
  • Relationship integrity (comments belonging to correct posts)
  • Expected data patterns

Let me know if you need any clarification or have questions about these tests!

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.

Actionable comments posted: 5

🧹 Nitpick comments (7)
r2dbc/boot-r2dbc-json-column/Dockerfile (3)

1-14: Add security and operational best practices

Consider adding the following best practices:

  1. Run as non-root user for security
  2. Expose the application port
  3. Add health check

Apply these additions:

 FROM eclipse-temurin:21.0.5_11-jre-alpine
 WORKDIR /app
+RUN addgroup -S spring && adduser -S spring -G spring
+USER spring:spring
 COPY --from=builder application/dependencies/ ./
 COPY --from=builder application/spring-boot-loader/ ./
 COPY --from=builder application/snapshot-dependencies/ ./
 COPY --from=builder application/application/ ./
+EXPOSE 8080
+HEALTHCHECK --interval=30s --timeout=3s \
+  CMD wget -q --spider http://localhost:8080/actuator/health || exit 1
 ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
🧰 Tools
🪛 Hadolint (2.12.0)

[error] 2-2: Use absolute WORKDIR

(DL3000)


[error] 9-9: Use absolute WORKDIR

(DL3000)


1-1: Consider using distroless base image

The Alpine-based JRE image is good, but Google's distroless images can provide even better security by having minimal attack surface.

Apply this diff to both FROM statements:

-FROM eclipse-temurin:21.0.5_11-jre-alpine
+FROM gcr.io/distroless/java21:nonroot

Also applies to: 8-8


14-14: Consider adding JVM tuning parameters

The ENTRYPOINT could benefit from JVM tuning parameters for containerized environments.

Apply this diff:

-ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
+ENTRYPOINT ["java", \
+  "-XX:MaxRAMPercentage=75.0", \
+  "-XX:InitialRAMPercentage=50.0", \
+  "-Djava.security.egd=file:/dev/./urandom", \
+  "org.springframework.boot.loader.launch.JarLauncher"]
r2dbc/boot-r2dbc-json-column/docker/docker-compose-app.yml (1)

17-21: Consider increasing the memory limit for the Spring Boot application.

The current memory limit of 512M might be insufficient for a Spring Boot application, especially during peak loads or when handling large datasets with R2DBC. Consider increasing it to at least 768M or 1G for better performance and stability.

     resources:
       limits:
-         memory: 512M
+         memory: 1G
          cpus: '1'
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (1)

92-95: Enhance error response structure.

Consider using a structured error response for better client handling:

-                    return ServerResponse.badRequest().bodyValue(e.getMessage());
+                    var errorResponse = Map.of(
+                        "error", "Bad Request",
+                        "message", e.getMessage(),
+                        "timestamp", System.currentTimeMillis()
+                    );
+                    return ServerResponse.badRequest().bodyValue(errorResponse);
r2dbc/boot-r2dbc-json-column/src/main/resources/db/migration/postgresql/V1__01_schema.sql (2)

4-9: Well-structured ENUM type creation!

Good choice using PostgreSQL ENUM type instead of VARCHAR. This provides:

  • Type safety at the database level
  • Reduced storage space compared to VARCHAR
  • Prevention of invalid status values

Consider documenting the intended lifecycle flow (DRAFT → PUBLISHED → ARCHIVED) in a comment, as it's not immediately obvious if posts can move directly from DRAFT to ARCHIVED.


15-25: Robust text field constraints!

Good implementation of text validation:

  • Prevents empty or whitespace-only content
  • Reasonable title length limits
  • Uses TEXT type for better content handling

Consider adding a comment explaining the business rules behind the 500-character title limit for future maintainers.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa0b42b and 08ce013.

📒 Files selected for processing (6)
  • r2dbc/boot-r2dbc-json-column/Dockerfile (1 hunks)
  • r2dbc/boot-r2dbc-json-column/docker/docker-compose-app.yml (2 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (3 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/resources/db/migration/postgresql/V1__01_schema.sql (2 hunks)
  • r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/ApplicationIntTest.java (1 hunks)
  • r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/handler/PostHandlerIntTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/handler/PostHandlerIntTest.java
🧰 Additional context used
📓 Learnings (1)
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (3)
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-11T14:14:45.469Z
Learning: The pagination implementation in Spring WebFlux R2DBC applications should include:
1. Page and size parameters in the request
2. Repository method with Pageable parameter
3. Response with both data and pagination metadata (pageNumber, pageSize, totalElements, totalPages)
4. Efficient fetching of related entities for the paginated results
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-12T05:53:38.355Z
Learning: Integration tests for paginated endpoints in Spring WebFlux should verify:
1. Default pagination behavior
2. Custom page size and number
3. Response structure including metadata (totalElements, totalPages)
4. Edge cases like out-of-bounds pages
5. Data consistency across pages
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-12T05:27:36.588Z
Learning: In Spring WebFlux R2DBC applications, proper database-level pagination should use native SQL query with LIMIT and OFFSET clauses, as JPQL-style queries don't translate to efficient SQL pagination. The offset should be calculated as page * size.
🪛 Hadolint (2.12.0)
r2dbc/boot-r2dbc-json-column/Dockerfile

[error] 2-2: Use absolute WORKDIR

(DL3000)


[error] 9-9: Use absolute WORKDIR

(DL3000)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (8)
r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/ApplicationIntTest.java (3)

1-12: LGTM! Clean class structure and imports.

The class follows integration test best practices by extending AbstractIntegrationTest and including all necessary imports.


14-42: LGTM! Comprehensive pagination test.

The test thoroughly verifies pagination metadata and post content with robust assertions.


14-42: Add test coverage for missing scenarios.

While the pagination test is solid, the following test coverage is still needed:

  1. Title search functionality:
    • findByTitleContains
    • findByTitleLike
  2. Comment-related operations:
    • findByPostId
    • findAllByPostIdIn
  3. Additional pagination scenarios:
    • Different page sizes
    • Sorting functionality

Let me know if you need help implementing these test cases. I can provide the test implementations as shown in the previous review.

r2dbc/boot-r2dbc-json-column/docker/docker-compose-app.yml (1)

4-4: LGTM! Service name change is appropriate.

The new service name boot-r2dbc-json-column better reflects the service's purpose and follows Docker naming conventions.

r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (3)

28-29: LGTM! Well-structured logger implementation.

The logger is correctly implemented as a static final field following best practices.


41-62: LGTM! Efficient pagination implementation with proper database-level pagination.

The implementation correctly:

  1. Uses offset-based pagination at the database level
  2. Efficiently fetches comments only for paginated posts
  3. Returns a standardized PagedResult response

79-89: LGTM! Robust error handling for post updates.

The implementation correctly:

  1. Checks for post existence with proper 404 handling
  2. Clearly separates the update flow
  3. Maintains immutability of the original request object
r2dbc/boot-r2dbc-json-column/src/main/resources/db/migration/postgresql/V1__01_schema.sql (1)

27-27: Verify the impact of removing VARCHAR limits

The change from VARCHAR(255) to TEXT type removes the character limit constraints. While this provides more flexibility, ensure that:

  1. The application layer has appropriate validation
  2. The UI can handle displaying longer content
  3. API responses are properly paginated for large text fields
✅ Verification successful

VARCHAR limit removal is safe due to enum constraints

The status field is properly constrained by a Java enum Post.Status with only three possible values: DRAFT, PENDING_MODERATION, and PUBLISHED. This provides strict validation at the application level, making the removal of VARCHAR limits in the database safe since:

  • Only these predefined values can be assigned
  • All values are short and fixed-length
  • No UI or pagination concerns exist for such limited values
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the application has proper validation for text fields
rg -A 5 "class.*Post.*{" | rg -A 10 "@Size|@Length|@Column"

Length of output: 153


Script:

#!/bin/bash
# Find Post entity class and its fields
echo "=== Post Entity Class ==="
rg -l "class.*Post" --type java

echo -e "\n=== Post Status Field and Validation ==="
rg -A 10 "status|Status" --type java

echo -e "\n=== Validation Configurations ==="
rg "@Size|@Length|@Column" --type java

Length of output: 79580


Script:

#!/bin/bash
echo "=== Post Entity Implementation ==="
cat r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/entity/Post.java

echo -e "\n=== Status Field Validation ==="
rg -B 5 -A 5 "status.*DRAFT" --type java

Length of output: 4613

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.

Actionable comments posted: 3

♻️ Duplicate comments (1)
r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/handler/PostHandlerIntTest.java (1)

78-102: ⚠️ Potential issue

Fix inconsistent page numbering in custom page size test

The test has the same page numbering inconsistency as the default pagination test.

                 .value(pagedResult -> {
                     assertThat(pagedResult.totalElements()).isEqualTo(20);
-                    assertThat(pagedResult.pageNumber()).isEqualTo(1);
+                    assertThat(pagedResult.pageNumber()).isEqualTo(0);
                     assertThat(pagedResult.totalPages()).isEqualTo(4);
🧹 Nitpick comments (3)
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (1)

53-55: Improve error message for maximum page size limit.

The error message doesn't mention the maximum page size limit of 50, which could confuse API users.

-                        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Page size must be greater than 0");
+                        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, 
+                               "Page size must be between 1 and 50");
r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/handler/PostHandlerIntTest.java (2)

23-40: Enhance test data setup maintainability

Consider extracting test data creation to a separate method and improving StepVerifier assertions.

+    private static final int TOTAL_POSTS = 20;
+    
+    private List<Post> createTestPosts() {
+        return IntStream.range(1, TOTAL_POSTS + 1)
+                .mapToObj(i -> Post.builder()
+                        .title("Test Post " + i)
+                        .content("Content " + i)
+                        .build())
+                .collect(Collectors.toList());
+    }
+
     @BeforeEach
     void setUp() {
-        // Create test data
-        var posts = IntStream.range(1, 21) // Create 20 posts
-                .mapToObj(i -> Post.builder()
-                        .title("Test Post " + i)
-                        .content("Content " + i)
-                        .build())
-                .collect(Collectors.toList());
+        var posts = createTestPosts();
 
         // Clear existing data and save new test data
         StepVerifier.create(postRepository
                 .deleteAll()
                 .thenMany(postRepository.saveAll(posts))
                 .collectList())
-                .expectNextCount(1)
+                .expectNext(posts)  // Verify exact content
                 .verifyComplete();
     }

160-168: Add more invalid parameter test cases

Consider adding more test cases for invalid parameters:

  1. Negative size
  2. Zero size
  3. Non-numeric values
     @Test
     void shouldHandleInvalidPageParameters() {
+        // Test negative page
         this.webTestClient
                 .get()
                 .uri("/posts?page=-1&size=10")
                 .exchange()
                 .expectStatus()
                 .isBadRequest();
+
+        // Test negative size
+        this.webTestClient
+                .get()
+                .uri("/posts?page=0&size=-1")
+                .exchange()
+                .expectStatus()
+                .isBadRequest();
+
+        // Test zero size
+        this.webTestClient
+                .get()
+                .uri("/posts?page=0&size=0")
+                .exchange()
+                .expectStatus()
+                .isBadRequest();
+
+        // Test non-numeric values
+        this.webTestClient
+                .get()
+                .uri("/posts?page=abc&size=def")
+                .exchange()
+                .expectStatus()
+                .isBadRequest();
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08ce013 and b0a5015.

📒 Files selected for processing (6)
  • r2dbc/boot-r2dbc-json-column/Dockerfile (1 hunks)
  • r2dbc/boot-r2dbc-json-column/docker/docker-compose-app.yml (2 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (3 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/repository/PostRepository.java (1 hunks)
  • r2dbc/boot-r2dbc-json-column/src/main/resources/db/migration/postgresql/V1__01_schema.sql (2 hunks)
  • r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/handler/PostHandlerIntTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • r2dbc/boot-r2dbc-json-column/docker/docker-compose-app.yml
  • r2dbc/boot-r2dbc-json-column/src/main/resources/db/migration/postgresql/V1__01_schema.sql
  • r2dbc/boot-r2dbc-json-column/Dockerfile
🧰 Additional context used
📓 Learnings (3)
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/repository/PostRepository.java (1)
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-11T14:14:45.469Z
Learning: The pagination implementation in Spring WebFlux R2DBC applications should include:
1. Page and size parameters in the request
2. Repository method with Pageable parameter
3. Response with both data and pagination metadata (pageNumber, pageSize, totalElements, totalPages)
4. Efficient fetching of related entities for the paginated results
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (6)
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-11T14:14:45.469Z
Learning: The pagination implementation in Spring WebFlux R2DBC applications should include:
1. Page and size parameters in the request
2. Repository method with Pageable parameter
3. Response with both data and pagination metadata (pageNumber, pageSize, totalElements, totalPages)
4. Efficient fetching of related entities for the paginated results
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:40-40
Timestamp: 2025-01-12T07:34:53.917Z
Learning: In Spring WebFlux applications, pagination parameters should be strictly validated by throwing ResponseStatusException with BAD_REQUEST status for negative page numbers and non-positive page sizes, along with descriptive error messages, instead of silently using default values.
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:40-40
Timestamp: 2025-01-12T07:31:47.218Z
Learning: In Spring WebFlux applications with R2DBC, pagination parameters (page and size) should be validated to ensure page is non-negative and size is positive. Default values should be provided (page=0, size=10) when parameters are missing.
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:40-40
Timestamp: 2025-01-12T07:31:47.218Z
Learning: When implementing sorting in Spring WebFlux with R2DBC, the sort direction should be validated to accept only 'ASC' or 'DESC' values, with a default fallback (typically 'DESC'). The sort field should be validated against allowed columns to prevent SQL injection.
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-12T05:53:38.355Z
Learning: Integration tests for paginated endpoints in Spring WebFlux should verify:
1. Default pagination behavior
2. Custom page size and number
3. Response structure including metadata (totalElements, totalPages)
4. Edge cases like out-of-bounds pages
5. Data consistency across pages
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-12T05:27:36.588Z
Learning: In Spring WebFlux R2DBC applications, proper database-level pagination should use native SQL query with LIMIT and OFFSET clauses, as JPQL-style queries don't translate to efficient SQL pagination. The offset should be calculated as page * size.
r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/handler/PostHandlerIntTest.java (1)
Learnt from: rajadilipkolli
PR: rajadilipkolli/my-spring-boot-experiments#1636
File: r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java:32-46
Timestamp: 2025-01-12T05:53:38.355Z
Learning: Integration tests for paginated endpoints in Spring WebFlux should verify:
1. Default pagination behavior
2. Custom page size and number
3. Response structure including metadata (totalElements, totalPages)
4. Edge cases like out-of-bounds pages
5. Data consistency across pages
🪛 GitHub Actions: boot-r2dbc-json-column
r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/handler/PostHandlerIntTest.java

[error] 192-192: Test failure in shouldHandleMaxPageSize: Status expected:<200 OK> but was:<400 BAD_REQUEST>


[error] 179-181: Test failure in shouldSortPostsByTitleAscending: Posts are not properly sorted. 'Test Post 9' should be less than 'Test Post 10' in ascending order

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (8)
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/handler/PostHandler.java (3)

5-28: LGTM! Well-structured imports and logger setup.

The new imports and logger setup are appropriate for the enhanced functionality.


66-90: LGTM! Efficient implementation of pagination and comment fetching.

The implementation correctly:

  1. Uses database-level pagination
  2. Fetches comments for paginated posts only
  3. Constructs a proper paged response

107-123: LGTM! Robust error handling in update method.

The implementation properly handles:

  1. Not found scenarios with appropriate status code
  2. Validation errors with logging
  3. Clean update flow with proper separation of concerns
r2dbc/boot-r2dbc-json-column/src/main/java/com/example/learning/repository/PostRepository.java (2)

12-12: LGTM! Clean interface definition

The removal of ReactiveQueryByExampleExecutor simplifies the interface while maintaining core R2DBC repository functionality.


21-31: ⚠️ Potential issue

Address SQL injection vulnerability and improve query performance

The current implementation has several concerns:

  1. SQL Injection Risk: The sortBy parameter is directly interpolated into the ORDER BY clause without validation
  2. No validation on sort parameters
  3. Suboptimal query performance due to CASE expressions in ORDER BY

Consider this safer implementation:

    @Query(
            """
-           SELECT * FROM posts ORDER BY CASE WHEN :direction = 'ASC' THEN :sortBy END ASC,
-           CASE WHEN :direction = 'DESC' THEN :sortBy END DESC
+           SELECT p.* FROM posts p
+           ORDER BY 
+           CASE :sortBy 
+               WHEN 'id' THEN p.id
+               WHEN 'title' THEN p.title
+               WHEN 'content' THEN p.content
+               WHEN 'created_at' THEN p.created_at
+               ELSE p.id
+           END
+           #{#direction == 'DESC' ? 'DESC' : 'ASC'}
            LIMIT :size OFFSET :offset
            """)

Additionally, add parameter validation in the service layer:

private void validateSortParams(String sortBy, String direction) {
    Set<String> validColumns = Set.of("id", "title", "content", "created_at");
    if (!validColumns.contains(sortBy)) {
        throw new IllegalArgumentException("Invalid sort column: " + sortBy);
    }
    if (!Set.of("ASC", "DESC").contains(direction.toUpperCase())) {
        throw new IllegalArgumentException("Invalid sort direction: " + direction);
    }
}

Let's verify the current usage:

r2dbc/boot-r2dbc-json-column/src/test/java/com/example/learning/handler/PostHandlerIntTest.java (3)

138-158: ⚠️ Potential issue

Fix out of bounds page handling

The test has several issues:

  1. Page number should be 10, not 11 (zero-based)
  2. Empty page should return empty list, not null
  3. Total pages calculation is incorrect for size=10 and total=20
                 .value(pagedResult -> {
                     assertThat(pagedResult.totalElements()).isEqualTo(20);
-                    assertThat(pagedResult.pageNumber()).isEqualTo(11);
+                    assertThat(pagedResult.pageNumber()).isEqualTo(10);
-                    assertThat(pagedResult.totalPages()).isEqualTo(2);
+                    assertThat(pagedResult.totalPages()).isEqualTo(3);
                     assertThat(pagedResult.isFirst()).isEqualTo(false);
                     assertThat(pagedResult.isLast()).isEqualTo(true);
                     assertThat(pagedResult.hasNext()).isEqualTo(false);
                     assertThat(pagedResult.hasPrevious()).isEqualTo(true);
                     List<Post> posts = pagedResult.data();
-                    assertThat(posts).isNull();
+                    assertThat(posts).isNotNull().isEmpty();

Likely invalid or redundant comment.


42-76: ⚠️ Potential issue

Fix inconsistent page numbering in default pagination test

The test asserts page number as 1 but Spring's pagination is zero-based by default.

     @Test
     void shouldReturnDefaultPagination() {
         this.webTestClient
                 .get()
                 .uri("/posts")
                 .exchange()
                 .expectStatus()
                 .isOk()
                 .expectBody(new ParameterizedTypeReference<PagedResult<Post>>() {})
                 .value(pagedResult -> {
                     assertThat(pagedResult.totalElements()).isEqualTo(20);
-                    assertThat(pagedResult.pageNumber()).isEqualTo(1);
+                    assertThat(pagedResult.pageNumber()).isEqualTo(0);
                     assertThat(pagedResult.totalPages()).isEqualTo(2);
                     assertThat(pagedResult.isFirst()).isEqualTo(true);
                     assertThat(pagedResult.isLast()).isEqualTo(false);
                     assertThat(pagedResult.hasNext()).isEqualTo(true);
                     assertThat(pagedResult.hasPrevious()).isEqualTo(false);
                     List<Post> posts = pagedResult.data();
                     assertThat(posts).isNotNull().isNotEmpty().hasSize(10);

Likely invalid or redundant comment.


104-136: ⚠️ Potential issue

Fix page numbering and content verification in second page test

The test has incorrect page numbering and content verification:

  1. For page=1 (second page), pageNumber should be 1, not 2
  2. Content verification starts from wrong post number
                 .value(pagedResult -> {
                     assertThat(pagedResult.totalElements()).isEqualTo(20);
-                    assertThat(pagedResult.pageNumber()).isEqualTo(2);
+                    assertThat(pagedResult.pageNumber()).isEqualTo(1);
                     assertThat(pagedResult.totalPages()).isEqualTo(3);
                     assertThat(pagedResult.isFirst()).isEqualTo(false);
                     assertThat(pagedResult.isLast()).isEqualTo(false);
                     assertThat(pagedResult.hasNext()).isEqualTo(true);
                     assertThat(pagedResult.hasPrevious()).isEqualTo(true);
                     List<Post> posts = pagedResult.data();
                     assertThat(posts).isNotNull().isNotEmpty().hasSize(8);
                     // Verify content
                     assertThat(posts)
                             .extracting(Post::getTitle)
                             .containsExactly(
-                                    "Test Post 9",
-                                    "Test Post 10",
-                                    "Test Post 11",
-                                    "Test Post 12",
-                                    "Test Post 13",
-                                    "Test Post 14",
-                                    "Test Post 15",
-                                    "Test Post 16"
+                                    "Test Post 8",
+                                    "Test Post 9",
+                                    "Test Post 10",
+                                    "Test Post 11",
+                                    "Test Post 12",
+                                    "Test Post 13",
+                                    "Test Post 14",
+                                    "Test Post 15"
                             );

Likely invalid or redundant comment.

@rajadilipkolli rajadilipkolli merged commit 0284233 into main Jan 12, 2025
7 of 8 checks passed
@rajadilipkolli rajadilipkolli deleted the polish branch January 12, 2025 08:18
@rajadilipkolli
Copy link
Owner Author

@coderabbitai , please help me to fill ReadMe file with contents of this repo and sequence diagram

Copy link
Contributor

coderabbitai bot commented Jan 12, 2025

Tip

For best results, initiate chat on the files or code changes.

@rajadilipkolli Here's the README content for your repository:

# Spring Boot R2DBC with JSON Column

This project demonstrates a reactive Spring Boot application using R2DBC with PostgreSQL JSON column support. It implements a blog-like system with posts and comments, featuring pagination and reactive endpoints.

## Features

- Reactive REST API using Spring WebFlux
- R2DBC with PostgreSQL for reactive database operations
- JSON column support for flexible data storage
- Flyway database migrations
- Pagination and sorting support
- Docker support with health checks
- Comprehensive test coverage using TestContainers
- Metrics and tracing with Micrometer

## Prerequisites

- JDK 21
- Docker and Docker Compose
- PostgreSQL 15+

## Getting Started

1. Clone the repository
2. Run PostgreSQL using Docker:
   ```bash
   docker-compose -f docker/docker-compose.yml up -d
  1. Run the application:
    ./mvnw spring-boot:run

API Endpoints

  • GET /posts?page=0&size=10&sort=title&direction=ASC - Get paginated posts
  • GET /posts/{id} - Get a specific post
  • POST /posts - Create a new post
  • PUT /posts/{id} - Update a post
  • DELETE /posts/{id} - Delete a post
  • GET /posts/{postId}/comments - Get comments for a post
  • POST /posts/{postId}/comments - Add a comment to a post

Architecture

sequenceDiagram
    participant Client
    participant WebRouter
    participant PostHandler
    participant PostRepository
    participant CommentRepository
    participant Database

    Client->>WebRouter: GET /posts?page=0&size=10
    WebRouter->>PostHandler: route to handler
    PostHandler->>PostRepository: findAllWithPagination(offset, size, sort, direction)
    PostRepository->>Database: Execute query
    Database-->>PostRepository: Return posts
    PostHandler->>CommentRepository: findAllByPostIdIn(postIds)
    CommentRepository->>Database: Execute query
    Database-->>CommentRepository: Return comments
    PostHandler->>PostHandler: Combine posts with comments
    PostHandler-->>Client: Return PagedResult<Post>
Loading

Running Tests

The project includes both unit and integration tests using TestContainers:

./mvnw verify

Docker Deployment

Build and run the application using Docker:

./mvnw spring-boot:build-image
docker-compose -f docker/docker-compose-app.yml up -d

Monitoring

The application exposes various actuator endpoints for monitoring:

  • Health: /actuator/health
  • Metrics: /actuator/metrics
  • Info: /actuator/info

Configuration

Key application properties can be configured in application.properties:

  • Database connection settings
  • Logging levels
  • Jackson JSON settings
  • Management endpoints

Would you like me to add any specific sections or modify the existing content?

<details>
<summary>🔗 Analysis chain</summary>





</details>

<details>
<summary>🏁 Scripts executed</summary>

The following scripts were executed for the analysis:

Script:

#!/bin/bash

Check if README.md exists and get its content

echo "=== README.md content ==="
cat r2dbc/boot-r2dbc-json-column/README.md 2>/dev/null || echo "File not found"

echo -e "\n=== Project structure ==="
tree r2dbc/boot-r2dbc-json-column/src/main -L 3

echo -e "\n=== Dependencies ==="
grep -A 2 "" r2dbc/boot-r2dbc-json-column/pom.xml

echo -e "\n=== Application properties ==="
cat r2dbc/boot-r2dbc-json-column/src/main/resources/application.properties 2>/dev/null || echo "File not found"
cat r2dbc/boot-r2dbc-json-column/src/main/resources/application.yml 2>/dev/null || echo "File not found"


Length of output: 4334

</details>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: r2dbc type: dependency-upgrade Pull requests that update a dependency file
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant