Skip to content

Conversation

@yruslan
Copy link
Collaborator

@yruslan yruslan commented Aug 25, 2025

Closes #776

Summary by CodeRabbit

  • New Features

    • Added packed decimal (COMP-3/COMP-3U) encoding for numeric fields with sign, precision, scale handling.
    • Encoder selector now exposes a packed-decimal encoder option.
  • Refactor

    • Centralized low-level field-setting and a helper to derive PIC/usage for clearer error messages.
  • Tests

    • New unit tests for packed-decimal encoding (many edge cases).
    • Updated extractor expectations and an integration test asserting byte-accurate EBCDIC output.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 25, 2025

Walkthrough

Adds packed BCD (COMP-3/COMP-3U) encoding and encoder selection, relocates setPrimitiveField to Copybook companion object, updates writer integration and diagnostics, and introduces unit tests covering BCD encoding and writer byte-level verification.

Changes

Cohort / File(s) Change Summary
Packed BCD encoder & selector
cobol-parser/src/main/scala/.../encoding/BCDNumberEncoders.scala, cobol-parser/src/main/scala/.../encoding/EncoderSelector.scala
New BCDNumberEncoders.encodeBCDNumber added; EncoderSelector gains getBdcEncoder and branches to return BCD encoders for COMP-3/COMP-3U with appropriate parameters.
Copybook refactor
cobol-parser/src/main/scala/.../Copybook.scala
setPrimitiveField moved from class Copybook to object Copybook; class imports companion for unqualified usage.
Writer integration & diagnostics
spark-cobol/src/main/scala/.../writer/BasicRecordCombiner.scala
Added object BasicRecordCombiner with getFieldDefinition(Primitive): String; calls switched from instance copybook.setPrimitiveField to Copybook.setPrimitiveField; error messages now show derived PIC/usage.
Unit tests: BCD encoder
cobol-parser/src/test/scala/.../encoding/BCDNumberEncodersSuite.scala
New test suite covering signed/unsigned, integral/decimal, scales, padding, sign nibble rules, and edge cases for encodeBCDNumber.
Unit tests: writer byte-level
spark-cobol/src/test/scala/.../writer/FixedLengthEbcdicWriterSuite.scala
New test verifying exact output bytes for rows with COMP-3 and COMP-3U fields.
Unit tests: extractor expectations
cobol-parser/src/test/scala/.../extract/BinaryExtractorSpec.scala
Updated three assertions to expect a non-empty encoder for COMPANY.COMPANY-ID-NUM.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant DF as Spark DataFrame Row
  participant BRC as BasicRecordCombiner
  participant ES as EncoderSelector
  participant BCD as BCDNumberEncoders
  participant CB as Copybook (object)

  DF->>BRC: iterate fields & values
  BRC->>ES: getEncoder(field metadata)
  ES-->>ES: detect COMP-3 / COMP-3U case
  ES->>BCD: encodeBCDNumber(value, precision, scale, scaleFactor, signed, mandatorySignNibble)
  BCD-->>ES: Array[Byte] (packed BCD)
  ES-->>BRC: encoder -> bytes
  BRC->>CB: Copybook.setPrimitiveField(field, recordBytes, bytes, offset)
  CB-->>BRC: updated recordBytes
  BRC-->>DF: produce row bytes for writer
  note right of BCD: New packed-decimal encoding path
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Add support for COMP-3 types in the writer (#776)
Integrate encoder selection for COMP-3/COMP-3U in writer path (#776)
Provide tests validating COMP-3 encoding in output (#776)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Relocated setPrimitiveField to companion object (cobol-parser/src/main/scala/.../Copybook.scala) Refactor of method location; not required by COMP-3 feature objective—affects call sites but does not change functionality.
Added getFieldDefinition(Primitive) helper in BasicRecordCombiner (spark-cobol/src/main/scala/.../writer/BasicRecordCombiner.scala) Diagnostic/message formatting helper; not necessary to implement COMP-3 encoding itself.

Possibly related PRs

Poem

In PIC fields I hop and neatly nibble,
Two digits per byte, a compact little dribble.
COMP-3 snug and signs tucked tight,
Tests pass under moonlit byte.
Carrots for code, bytes snug and right. 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 061c928 and dc04925.

📒 Files selected for processing (3)
  • cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncoders.scala (1 hunks)
  • cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncodersSuite.scala (1 hunks)
  • spark-cobol/src/main/scala/za/co/absa/cobrix/spark/cobol/writer/BasicRecordCombiner.scala (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncodersSuite.scala
  • cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncoders.scala
  • spark-cobol/src/main/scala/za/co/absa/cobrix/spark/cobol/writer/BasicRecordCombiner.scala
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (2.12.20, 2.12, 3.3.4, 0, 80, 20)
  • GitHub Check: Spark 3.5.5 on Scala 2.13.16
  • GitHub Check: Spark 2.4.8 on Scala 2.11.12
  • GitHub Check: Spark 3.5.5 on Scala 2.12.20
  • GitHub Check: Spark 3.4.4 on Scala 2.12.20
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/776-add-comp-3-type

🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary or {PR 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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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
Copy link

github-actions bot commented Aug 25, 2025

JaCoCo code coverage report - 'cobol-parser'

Overall Project 91.7% -0.11% 🍏
Files changed 85.81% 🍏

File Coverage
BCDNumberEncoders.scala 99.18% -0.82% 🍏
EncoderSelector.scala 97.26% -2.28% 🍏
Copybook.scala 81.71% -5.74% 🍏

@github-actions
Copy link

github-actions bot commented Aug 25, 2025

JaCoCo code coverage report - 'spark-cobol'

File Coverage [58.95%]
BasicRecordCombiner.scala 58.95%
Total Project Coverage 79.83% 🍏

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: 0

🧹 Nitpick comments (10)
spark-cobol/src/main/scala/za/co/absa/cobrix/spark/cobol/writer/BasicRecordCombiner.scala (1)

37-38: Minor formatting improvement in pattern matching.

The pattern matching for Group filtering is formatted consistently with the rest of the codebase.

cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncoders.scala (6)

25-26: Scaladoc formula and param phrasing are misleading for even precision with a sign nibble

  • The array length is not always (precision + 1) / 2. For even precision with a mandatory sign nibble, an extra pad nibble is required, making length = (precision + 2) / 2. For unsigned (no sign nibble) it’s ceil(precision / 2).
  • “before of after the decimal point” is a typo; also clarify that scaleFactor is applied relative to scale via movePointLeft(scaleFactor - scale).

Apply this comment-only diff to tighten the docs:

-   * The length of the output array is determined by the formula: (precision + 1) / 2.
+   * Output length (bytes):
+   * - Unsigned (no sign nibble): ceil(precision / 2).
+   * - Signed (mandatory sign nibble): ceil((precision + 1) / 2).
+   *   Note: for even precision a pad nibble precedes the trailing sign nibble.
@@
-   * @param scale               A decimal scale if a number is a decimal. Should be greater or equal to zero.
-   * @param scaleFactor         Additional zeros to be added before of after the decimal point.
+   * @param scale               Decimal scale of the PIC (>= 0).
+   * @param scaleFactor         Applied relative to `scale` as movePointLeft(scaleFactor - scale).
+   *                            Positive values shift the decimal point left, negative – right.

Also applies to: 30-35


42-44: Validate input invariants early (scale, precision) for fail-fast behavior

Precision check is good. Consider also guarding against invalid scale and extremely large precision to fail fast with a clear message, rather than silently returning zeros later.

   def encodeBCDNumber(number: java.math.BigDecimal,
                       precision: Int,
                       scale: Int,
                       scaleFactor: Int,
                       signed: Boolean,
                       mandatorySignNibble: Boolean): Array[Byte] = {
-    if (precision < 1)
+    if (precision < 1)
       throw new IllegalArgumentException(s"Invalid BCD precision=$precision, should be greater than zero.")
+    if (scale < 0)
+      throw new IllegalArgumentException(s"Invalid BCD scale=$scale, should be >= 0.")
+    // Optional: place an upper bound to prevent pathological allocations
+    if (precision > 38)
+      throw new IllegalArgumentException(s"Unsupported BCD precision=$precision, max supported is 38.")

45-52: totalDigits computation is correct; consider extracting for reuse and readability

The parity handling for the sign nibble and pad nibble is correct. Encapsulating this in a small helper improves readability and reduces mental load.

-    val totalDigits = if (mandatorySignNibble) {
-      if (precision % 2 == 0) precision + 2 else precision + 1
-    } else {
-      if (precision % 2 == 0) precision else precision + 1
-    }
+    def totalNibbles(precision: Int, hasSignNibble: Boolean): Int = {
+      val even = (precision & 1) == 0
+      if (hasSignNibble) if (even) precision + 2 else precision + 1
+      else               if (even) precision     else precision + 1
+    }
+    val totalDigits = totalNibbles(precision, mandatorySignNibble)

58-62: Normalize once and avoid scientific notation by using toPlainString

Two minor improvements:

  • Compute the shift once.
  • Use toPlainString to avoid accidental scientific notation (defensive, even though scale=0 typically prints plain).
-    val integralNumberStr = if (scaleFactor - scale == 0)
-      number.setScale(0, RoundingMode.HALF_DOWN).toString
-    else
-      number.movePointLeft(scaleFactor - scale).setScale(0, RoundingMode.HALF_DOWN).toString
+    val shift = scaleFactor - scale
+    val shifted = if (shift == 0) number else number.movePointLeft(shift)
+    val integralNumberStr = shifted.setScale(0, RoundingMode.HALF_DOWN).toPlainString

63-69: Determine sign numerically to avoid string-based edge cases (-0, locale)

Using String prefix works here, but a numeric sign check after rounding is more robust and avoids surprises with “-0”.

-    val isNegative = integralNumberStr.startsWith("-")
-    val digitsOnly = integralNumberStr.stripPrefix("-").stripPrefix("+")
+    val rounded = new java.math.BigDecimal(integralNumberStr) // scale 0 already
+    val isNegative = rounded.signum() < 0
+    val digitsOnly = integralNumberStr.stripPrefix("-")

Also applies to: 101-103


76-89: Overflow-to-zero behavior is by design — consider an opt-in strict mode

When digitsOnly.length > precision, returning zeros is consistent with other branches. For auditability, a strict mode (throwing or logging a structured warning) can help detect data truncation early in dev/test while keeping prod permissive.

I can wire an optional strict: Boolean = false param (defaulting to current behavior) and add tests. Interested?

cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncodersSuite.scala (3)

118-123: Add tie-breaking tests to lock in HALF_DOWN semantics (+1.5/-1.5)

Given the encoder uses HALF_DOWN, add explicit tie cases to prevent regressions (it’s easy for someone to “optimize” rounding later).

@@ "decimal number" when {
+      "rounds half-down for +1.5" in {
+        // shift = 0 => scale to 0 using HALF_DOWN: 1.5 -> 1
+        val expected = Array[Byte](0x1C.toByte)
+        val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal("1.5"), 1, 0, 0, signed = true, mandatorySignNibble = true)
+        checkExpected(actual, expected)
+      }
+
+      "rounds half-down for -1.5" in {
+        // shift = 0 => scale to 0 using HALF_DOWN: -1.5 -> -1
+        val expected = Array[Byte](0x1D.toByte)
+        val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal("-1.5"), 1, 0, 0, signed = true, mandatorySignNibble = true)
+        checkExpected(actual, expected)
+      }

Also applies to: 153-158


95-101: Consider asserting on null handling explicitly

The production code returns zero bytes for null; add a test to make this behavior explicit.

@@
       "attempt to encode a number with an incorrect precision with sign nibble" in  {
         val expected = Array[Byte](0x00, 0x00, 0x00)
         val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(12345), 4, 0, 0, signed = true, mandatorySignNibble = true)
 
         checkExpected(actual, expected)
       }
+
+      "encode null as zeros" in {
+        val expected = Array[Byte](0x00, 0x00, 0x00)
+        val actual = BCDNumberEncoders.encodeBCDNumber(null, 5, 0, 0, signed = true, mandatorySignNibble = true)
+        checkExpected(actual, expected)
+      }

Also applies to: 102-107


190-198: Nit: toHex rendering is solid; consider including precision/scale in the failure message

Minor ergonomics improvement for debugging failures.

-      fail(s"Actual: $actualHex\nExpected: $expectedHex")
+      fail(s"Actual: $actualHex\nExpected: $expectedHex")

If you’d like, I can update call sites to pass context (precision/scale/flags) and include it here.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7f63354 and 061c928.

📒 Files selected for processing (7)
  • cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/Copybook.scala (2 hunks)
  • cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncoders.scala (1 hunks)
  • cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/encoding/EncoderSelector.scala (3 hunks)
  • cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncodersSuite.scala (1 hunks)
  • cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/extract/BinaryExtractorSpec.scala (3 hunks)
  • spark-cobol/src/main/scala/za/co/absa/cobrix/spark/cobol/writer/BasicRecordCombiner.scala (4 hunks)
  • spark-cobol/src/test/scala/za/co/absa/cobrix/spark/cobol/writer/FixedLengthEbcdicWriterSuite.scala (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncoders.scala (1)
cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/parse/FieldSizeSpec.scala (1)
  • scale (63-74)
spark-cobol/src/test/scala/za/co/absa/cobrix/spark/cobol/writer/FixedLengthEbcdicWriterSuite.scala (2)
spark-cobol/src/test/scala/za/co/absa/cobrix/spark/cobol/source/fixtures/BinaryFileFixture.scala (1)
  • withTempDirectory (71-78)
spark-cobol/src/main/scala/za/co/absa/cobrix/spark/cobol/writer/RawBinaryOutputFormat.scala (2)
  • write (49-53)
  • close (54-56)
cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/encoding/EncoderSelector.scala (4)
cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/ast/datatype/Usage.scala (2)
  • COMP3 (30-32)
  • COMP3U (34-36)
cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/ast/datatype/Decimal.scala (1)
  • Decimal (32-63)
cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/ast/datatype/Integral.scala (1)
  • Integral (30-40)
cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncoders.scala (2)
  • BCDNumberEncoders (21-108)
  • encodeBCDNumber (36-106)
cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncodersSuite.scala (1)
cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncoders.scala (2)
  • BCDNumberEncoders (21-108)
  • encodeBCDNumber (36-106)
spark-cobol/src/main/scala/za/co/absa/cobrix/spark/cobol/writer/BasicRecordCombiner.scala (5)
cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/Copybook.scala (3)
  • Copybook (28-347)
  • Copybook (350-446)
  • setPrimitiveField (427-445)
cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/ast/Primitive.scala (2)
  • Primitive (42-140)
  • toString (67-69)
cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/ast/datatype/AlphaNumeric.scala (1)
  • AlphaNumeric (28-36)
cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/ast/datatype/Decimal.scala (1)
  • Decimal (32-63)
cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/ast/datatype/Integral.scala (1)
  • Integral (30-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Spark 3.5.5 on Scala 2.12.20
  • GitHub Check: Spark 3.4.4 on Scala 2.12.20
  • GitHub Check: Spark 3.5.5 on Scala 2.13.16
  • GitHub Check: Spark 2.4.8 on Scala 2.11.12
  • GitHub Check: test (2.12.20, 2.12, 3.3.4, 0, 80, 20)
🔇 Additional comments (19)
cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/Copybook.scala (2)

28-29: LGTM! Clean refactoring implementation.

The import statement properly enables unqualified access to the companion object's methods after the setPrimitiveField method relocation.


414-445: Well-structured method relocation to companion object.

The setPrimitiveField method has been properly moved from the instance to the companion object, maintaining the same signature and functionality. The method includes appropriate boundary checks, size validation, and error handling for field encoding operations.

cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/extract/BinaryExtractorSpec.scala (3)

219-219: Test assertion updated to reflect COMP-3 encoder availability.

The test now correctly expects a non-empty encoder for the COMP-3 field, validating that the new BCD encoding support is properly integrated.


233-233: Consistent test validation for COMP-3 encoder.

The assertion properly verifies encoder availability for COMP-3 fields in the padding test scenario.


247-247: Consistent test validation continues for truncation scenario.

The assertion maintains consistency with other test cases by verifying COMP-3 encoder availability.

spark-cobol/src/test/scala/za/co/absa/cobrix/spark/cobol/writer/FixedLengthEbcdicWriterSuite.scala (1)

129-184: Comprehensive COMP-3 encoding test with precise byte-level validation.

This test provides excellent coverage for the new COMP-3/COMP-3U functionality by:

  • Testing both signed (COMP-3) and unsigned (COMP-3U) packed decimal fields
  • Including various numeric types (Int, Double, BigDecimal) and both positive/negative values
  • Performing exact byte-level verification against expected EBCDIC encoding
  • Including diagnostic output for debugging failed assertions

The test data covers edge cases like decimal precision, scale factors, and mixed sign scenarios, ensuring robust validation of the BCD encoding implementation.

cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/encoding/EncoderSelector.scala (3)

19-19: Proper imports added for COMP-3 support.

The new imports correctly include the required types (COMP3, COMP3U, Decimal, Integral) to support BCD encoding functionality.


34-41: Well-implemented COMP-3/COMP-3U encoder selection logic.

The pattern matching correctly handles both Integral and Decimal types with COMP-3/COMP-3U compact metadata. The logic properly:

  • Differentiates between signed (COMP-3) and unsigned (COMP-3U) variants
  • Sets appropriate parameters for precision, scale, scaleFactor, and sign handling
  • Uses mandatorySignNibble = true for COMP-3 and false for COMP-3U as per BCD standards

91-111: Robust BCD encoder implementation with proper validation.

The getBdcEncoder method includes:

  • Proper parameter validation ensuring signed fields require mandatory sign nibbles
  • Comprehensive type conversion handling for various numeric types (BigDecimal, BigInteger, primitives)
  • Fallback to string conversion for unknown types
  • Clean delegation to the BCDNumberEncoders utility

The type conversion logic appropriately handles null values and covers all common numeric types that might be encountered in practice.

spark-cobol/src/main/scala/za/co/absa/cobrix/spark/cobol/writer/BasicRecordCombiner.scala (6)

21-21: Import added for companion object access.

The import enables usage of the relocated setPrimitiveField method from the Copybook companion object.


25-25: Additional imports for enhanced field definition formatting.

The imports support the new getFieldDefinition helper method for improved error message formatting.


31-31: Clean companion object import pattern.

The import statement allows unqualified access to companion object methods within the class.


48-51: Improved error messages with detailed field definitions.

The error message now includes more descriptive field information using the new getFieldDefinition helper, making it easier for users to understand which field types are not yet supported.


74-74: Updated call to use companion object method.

The call correctly uses Copybook.setPrimitiveField instead of the instance method, aligning with the method relocation.


120-132: Well-designed companion object with useful helper method.

The getFieldDefinition method provides a clean way to format field definitions for error messages by:

  • Preferring originalPic over pic for more accurate representation
  • Including usage information for numeric types (Integral/Decimal)
  • Handling non-numeric types gracefully
  • Returning a properly formatted string with trimmed whitespace

This enhances the user experience by providing more informative error messages when unsupported field types are encountered.

cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncoders.scala (3)

21-35: Good addition: focused, dependency-free COMP-3 encoder with clear parameters

The overall structure and responsibilities are clean. The encoder is self-contained, free of external deps, and the parameter list maps well to COBOL semantics. Nice job.


54-56: Null input returns zero bytes — confirm writer semantics

Returning zero-filled bytes for null aligns with tests in this PR. Please confirm this matches the writer contract elsewhere (e.g., BasicRecordCombiner and Copybook.setPrimitiveField) so nulls don’t silently mask data issues in “strict” pipelines.

Would you like a “strict mode” switch (throw on null/overflow) at the encoder level, defaulting to current permissive behavior?


79-89: Padding and packing logic look correct for all parity/sign cases

  • The “+ '0' placeholder nibble” and subsequent sign-nibble overwrite are correct and match COMP-3 expectations.
  • Loop packs two digits per byte with no off-by-one risks.

Also applies to: 93-99, 101-103

cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncodersSuite.scala (1)

22-107: Comprehensive suite covering signed/unsigned, scale/scaleFactor, and overflow paths

Great coverage across integral and decimal cases, including negative handling and size mismatches. The byte-level assertions with diagnostic hex are especially useful.

Also applies to: 110-187

@yruslan yruslan merged commit 504f7cf into master Aug 25, 2025
6 of 7 checks passed
@yruslan yruslan deleted the feature/776-add-comp-3-type branch August 25, 2025 08:15
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.

Add support for COMP-3 types in the writer

2 participants