Infotype Reading Pattern (IFHRPAREADINFOTYPE + Primary/Secondary + Buffering)
Infotype Reading Pattern (IF_HRPA_READ_INFOTYPE + Primary/Secondary + Buffering)
Status: Draft v1.0
Audience: Implementers of ZCL_HR_INFOTYPE_READER
Related: infotype_reader_spec.md, exception_class_spec.md
This document describes the internal reading pattern that ZCL_HR_INFOTYPE_READER uses to fetch raw infotype data before aggregation. It is derived from the production pattern captured in read_infotype_example.txt and adapted for range reads (BEGDA..ENDDA) rather than single-key-date reads.
The pattern is deliberately separated from the main class spec so the same approach can be reused by other HR reader utilities without duplicating the explanation.
1. Why CL_HRPA_READ_INFOTYPE and not HR_READ_INFOTYPE
HR_READ_INFOTYPE is a classic function module that pre-dates decoupled infotypes. On S/4HANA with the decoupled infotype framework enabled (this system has it enabled), the FM still works for simple primary infotypes but is in maintenance mode and fails silently or with cryptic errors for certain classes of infotypes — in particular those that have switched to a decoupled storage model.
CL_HRPA_READ_INFOTYPE is the current framework entry point. It returns an instance of the interface IF_HRPA_READ_INFOTYPE, which exposes:
READ— read one infotype into aPRELP_TABfor one employee, one infotype, optionally withSUBTYfilter, over a date rangeREAD_WIDE— read a primary infotype together with its linked secondary in a single call, returning pairs of rows (primary + secondary) inHRPAD_INFOTYPE_DATA_TAB
Both methods accept BEGDA/ENDDA as a range — this is used directly (the user requirement is a range, not a key date).
Obtain the instance once per reader:
DATA mo_infty_reader TYPE REF TO if_hrpa_read_infotype.
TRY.
cl_hrpa_read_infotype=>get_instance(
IMPORTING infotype_reader = mo_infty_reader ).
CATCH cx_hrpa_violated_assertion INTO DATA(lx_root).
RAISE EXCEPTION TYPE zcx_hr_infotype_reader
EXPORTING
textid = zcx_hr_infotype_reader=>reader_not_available
exc_no = zcx_hr_infotype_reader=>c_exc_no-reader_not_available
previous = lx_root.
ENDTRY.
Failure to obtain the instance is a hard error — the reader is unusable. Every other error mode is localized per infotype.
2. Primary vs Secondary infotypes
T777D-INFKN classifies infotypes (globally, independent of MOLGA):
INFKN | Meaning | Example | How it's read |
|---|---|---|---|
'I' | Primary / individual | 0001, 0002 | READ — unless it has a linked secondary (see below) |
'Z' | Secondary | 0003 | Not read directly — read via its owning primary with READ_WIDE |
| (blank or other) | Unknown | customer infotypes | Fallback to READ as primary |
Linkage between a primary and its secondary is MOLGA-dependent and is resolved via two tables:
- T582V: primary infotype → view identifier (
VINFT). Keyed by(MOLGA, INFTY). - T582W: view identifier → list of infotypes assigned to the view. Keyed by
(VINFT, INFTY).
The same VINFT appears in both tables; T582V lists only the primary, T582W lists all infotypes (including the primary itself) that belong to the view. The linked secondary is the row in T582W with the same VINFT but a different INFTY than the primary — provided that candidate's own T777D-INFKN is 'Z'.
Linkage resolution algorithm
Given infotype X and personnel MOLGA M:
- Look up
T777D-INFKNforX. Call itINFKN_X. - If
INFKN_X = 'I':SELECT SINGLE vinft FROM t582v WHERE infty = X AND molga = M. If nothing found,Xhas no linked secondary — it is a standalone primary.- If
vinftfound:SELECT SINGLE infty FROM t582w WHERE vinft = vinft AND infty <> X.- If nothing found, still standalone primary.
- If a candidate
Yis found, verifyT777D-INFKNforYequals'Z'. If yes,Yis the linked secondary. If not, discard and treatXas standalone primary.
- If
INFKN_X = 'Z':SELECT SINGLE vinft FROM t582w WHERE infty = X. If nothing found,Xis an orphan secondary — log warning, skip.- If
vinftfound:SELECT SINGLE infty FROM t582v WHERE vinft = vinft AND molga = M AND infty <> X.- If nothing found, orphan secondary — log warning, skip.
- If a candidate
Pis found, verifyT777D-INFKNforPequals'I'. If yes,Pis the owning primary. If not, discard and treatXas orphan.
- If
INFKN_Xis blank or another value, treatXas a standalone primary ('I'fallback).
This resolution must be cached at class level keyed by (INFTY, MOLGA). For one program run the metadata is static; re-running the three SELECT SINGLEs for every employee would be wasted I/O.
3. Read paths
3.1 Standalone primary — READ
Primary infotype with no linked secondary (either T582V has no entry for the given (MOLGA, INFTY), or the resolution rejected the candidate):
DATA lt_prelp TYPE prelp_tab.
DATA lv_missing_auth TYPE boolean.
TRY.
mo_infty_reader->read(
EXPORTING
tclas = cl_hrpa_tclas=>tclas_employee
pernr = iv_pernr
infty = iv_infty
subty = iv_subty " SPACE = no subtype filter
begda = iv_begda
endda = iv_endda
sprps = if_hrpa_read_infotype=>unlocked
no_auth_check = iv_no_auth_check
IMPORTING
infotype_tab = lt_prelp
missing_auth = lv_missing_auth ).
CATCH cx_hrpa_violated_assertion INTO DATA(lx).
RAISE EXCEPTION TYPE zcx_hr_infotype_reader
EXPORTING
textid = zcx_hr_infotype_reader=>read_failure
exc_no = zcx_hr_infotype_reader=>c_exc_no-read_failure
pernr = iv_pernr
infty = iv_infty
previous = lx.
ENDTRY.
SPRPS is the lock indicator of the returned records. IF_HRPA_READ_INFOTYPE only exposes UNLOCKED and LOCKED as named constants; there is no ALL constant. The class's c_sprps values map as follows (see SPRPS_TO_FRAMEWORK in the implementation):
c_sprps value | Framework value passed to READ / READ_WIDE |
|---|---|
unlocked ('0') | IF_HRPA_READ_INFOTYPE=>UNLOCKED |
locked ('1') | IF_HRPA_READ_INFOTYPE=>LOCKED |
all ('2') | literal '*' — wildcard meaning "all lock statuses" |
The class exposes this as an input parameter so callers that need full auditability (e.g. migration validation) can request all statuses. Default is UNLOCKED.
3.2 Primary with linked secondary — READ_WIDE
Returns rows of HRPAD_INFOTYPE_DATA_TAB where each row contains a PRIMARY_INFTY and a SECONDARY_INFTY field (both of type PRELP). One call yields both infotypes' data:
DATA lt_wide TYPE hrpad_infotype_data_tab.
DATA lv_missing_auth TYPE boolean.
TRY.
mo_infty_reader->read_wide(
EXPORTING
tclas = cl_hrpa_tclas=>tclas_employee
pernr = iv_pernr
infty = iv_primary_infty " the PRIMARY, even if caller asked for the secondary
sprps = if_hrpa_read_infotype=>unlocked
begda = iv_begda
endda = iv_endda
no_auth_check = iv_no_auth_check
IMPORTING
infotype_tab = lt_wide
missing_auth = lv_missing_auth ).
CATCH cx_hrpa_violated_assertion INTO DATA(lx).
RAISE EXCEPTION TYPE zcx_hr_infotype_reader
EXPORTING
textid = zcx_hr_infotype_reader=>read_failure
exc_no = zcx_hr_infotype_reader=>c_exc_no-read_failure
pernr = iv_pernr
infty = iv_primary_infty
previous = lx.
ENDTRY.
After the wide read, split lt_wide into two PRELP tables — one for the primary, one for the secondary — and insert both into the instance PRELP cache. The secondary entry is inserted even if the caller originally asked only for the primary, because a subsequent request for the secondary (within the same instance lifetime for the same employee) will then be served from the cache at no extra I/O.
Each row of HRPAD_INFOTYPE_DATA_TAB carries two nested PRELP records as the components PRIMARY_INFTY and SECONDARY_INFTY. The implementation splits the table by appending rows whose nested PRIMARY_INFTY-INFTY (resp. SECONDARY_INFTY-INFTY) is non-initial:
LOOP AT lt_wide INTO DATA(ls_wide).
IF ls_wide-primary_infty-infty IS NOT INITIAL. APPEND ls_wide-primary_infty TO lt_primary_prelp. ENDIF.
IF ls_wide-secondary_infty-infty IS NOT INITIAL. APPEND ls_wide-secondary_infty TO lt_secondary_prelp. ENDIF.
ENDLOOP.
Both tables are then cached via cache_put( pernr, infty, t_prelp ) — the secondary's infotype is taken from the linkage metadata, not from the record itself, so the cache key is consistent even when the framework returns an empty secondary side.
3.3 Secondary requested on its own
The only correct path is:
- Resolve the owning primary
Pvia T582W → T582V. READ_WIDEthe primary — this populates cache entries for bothPand the secondary.- Read the secondary from the cache.
ZCL_HR_INFOTYPE_READER must not attempt to read a secondary directly with READ — this produces either empty results or framework errors depending on the infotype.
3.4 Auto-expansion of linked infotypes
The top-level READ_AGGREGATED_* methods call EXPAND_REQUESTED_INFOTYPES before any read. This routine walks the caller's IT_INFOTYPES, resolves each entry's metadata, and appends the linked partner (primary ↔ secondary) to the effective request list when it is not already present. The partner is appended with mandatory = abap_false and no subtype filter.
Effect on the read flow:
- The
READ_WIDEcall is still issued exactly once per primary, because the per-infotype dispatcher sees the partner as already cached after the first wide read. - Both the requested infotype and the partner appear in the caller's result structure (see reader spec §6.4). Typed-mode callers must declare
T_IT<NNNN>components for the partners as well, or validation will raiseSTRUCTURE_MISMATCH.
This is a behavior of the aggregator, not of the framework — READ_WIDE always returns both sides; the aggregator now also surfaces both sides.
4. PRELP cache and PRELP → PNNNN conversion
Why PRELP is cached, not PNNNN
PRELP is the physical transport format used by the infotype framework. Converting to P<NNNN> (e.g. P0001, P0002) is done per call, not at cache-insert time, because:
- The target type in the caller's output differs per call (the caller may pass
P0001in one call and a custom flat structure in another). PRELPis a single generic type; caching it keeps the cache flat and type-agnostic.- Conversion overhead is negligible compared to DB I/O.
Conversion
cl_hr_pnnnn_type_cast=>prelp_to_pnnnn_tab(
EXPORTING prelp_tab = lt_prelp_from_cache
IMPORTING pnnnn_tab = ct_typed_tab ). " STANDARD TABLE OF P<NNNN>
CT_TYPED_TAB is typed generically as STANDARD TABLE at the method signature; the caller-supplied type is respected by the cast. If the target table's line type doesn't match P<NNNN> structurally, PRELP_TO_PNNNN_TAB will raise an assertion — catch it and map to STRUCTURE_MISMATCH.
Cache key and lifecycle
| Cache | Scope | Key | Lifetime |
|---|---|---|---|
GT_META_CACHE | Class-level | (INFTY, MOLGA) | Program run (shared across instances) |
MT_PRELP_CACHE | Instance | (PERNR, INFTY) | Reader instance lifetime |
The class-level meta cache is never cleared within a program run — T777D/T582V/T582W data is effectively static per MOLGA. The instance-level PRELP cache is cleared by CLEAR_BUFFER (all) or CLEAR_BUFFER_FOR_PERNR (one employee).
Empty-result caching
An empty PRELP result (lt_prelp IS INITIAL) is still cached as an empty entry. This distinguishes "read happened, nothing found" from "not yet read". The same applies to the missing_auth case — insert an empty entry for the infotype so the same employee is not re-tried within the instance lifetime.
5. Range reads — what "active within BEGDA..ENDDA" means
READ and READ_WIDE with a BEGDA..ENDDA range return every infotype record whose validity interval overlaps the requested window at least partially:
- Record with validity
[R_BEG, R_END]is returned whenR_BEG ≤ V_ENDANDR_END ≥ V_BEG.
The framework does not clip the returned record's own dates to the requested window — callers receive the record as stored. The aggregation step in ZCL_HR_INFOTYPE_READER is responsible for any window clipping and for the time-slicing described in the main class spec.
BEGDA/ENDDA sentinels
Two common sentinels appear in HR data:
BEGDA = '00010101'(earliest possible date) — records valid "from the beginning of time"ENDDA = '99991231'(latest possible date) — records valid "to the end of time"
The reader must handle these without special-casing — they are simply large/small values in date arithmetic.
6. Authorization — MISSING_AUTH is a warning, not an exception
Both READ and READ_WIDE return a MISSING_AUTH TYPE BOOLEAN flag. When set, the caller (employee) has no read authorization for the infotype, no data is returned, and the operation is not an error in the ABAP exception sense.
Policy for ZCL_HR_INFOTYPE_READER:
- Append a warning to
ET_MESSAGES(MSGTY = 'W') with the(PERNR, INFTY)context. - Insert an empty cache entry so the same infotype is not retried for the same employee.
- Do not raise. Continue with the next infotype.
- In the final output, the nested table for this infotype is empty.
Rationale: batch runs, especially reconciliation and data migration jobs, routinely encounter per-employee authorization gaps that the ops team expects to be reported, not aborted.
Opting out of authority checks
For background utility contexts where an overarching authorization concept has already been established (e.g. a report that already executed a PNP auth check, or a batch job under a service user with a documented blanket authorization), the factory offers iv_no_auth_check = abap_true. This sets the no_auth_check parameter on every framework call. Use with discipline — it should be an explicit, audited decision per caller, not a convenience default.
7. Resolving MOLGA
Linkage resolution needs the employee's country grouping. Determine it once per READ_AGGREGATED_* call from PA0001 valid at the input BEGDA (or ENDDA — either works for this purpose since MOLGA does not change for normal employees):
SELECT SINGLE molga FROM pa0001 INTO @mv_molga
WHERE pernr = @iv_pernr
AND begda <= @iv_begda
AND endda >= @iv_begda.
IF sy-subrc <> 0.
" Fall back to ENDDA or to the period that contains any date in the window
SELECT SINGLE molga FROM pa0001 INTO @mv_molga
WHERE pernr = @iv_pernr
AND begda <= @iv_endda
AND endda >= @iv_begda.
ENDIF.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE zcx_hr_infotype_reader
EXPORTING
textid = zcx_hr_infotype_reader=>invalid_pernr
exc_no = zcx_hr_infotype_reader=>c_exc_no-invalid_pernr
pernr = iv_pernr.
ENDIF.
If the employee has no PA0001 record overlapping the window, the PERNR is treated as invalid for this call — raise INVALID_PERNR. This is the one case where absence of data is a hard error rather than an empty result.
Store MV_MOLGA as an instance attribute; it is stable for the call.
8. Subtype filter
A SUBTY filter is per-infotype, not per-call. The main class's input table IT_INFOTYPES (type TY_INFOTYPE_REQ_T) carries an optional SUBTY per requested infotype. When reading, pass it to READ:
- If
SUBTY = SPACE— no subtype filter, read all subtypes. - Otherwise pass it as-is.
Caveat for READ_WIDE: the SAP API does not expose a SUBTY parameter on READ_WIDE. When reading a primary with linked secondary, all subtypes of both infotypes are fetched into the cache. The subtype filter is then applied at serve time — when converting the cached PRELP to typed rows for the caller. Filter by SUBTY field on the typed P<NNNN> rows after the cast.
Document this as a behavioral note on the IT_INFOTYPES parameter in the main spec.
9. Exception mapping table
Mapping of framework and local conditions to ZCX_HR_INFOTYPE_READER:
| Condition | Severity | textid | exc_no |
|---|---|---|---|
CL_HRPA_READ_INFOTYPE=>GET_INSTANCE raises | hard | READER_NOT_AVAILABLE | 10 |
No PA0001 record overlapping the input window | hard | INVALID_PERNR | 1 |
BEGDA > ENDDA or either is initial | hard | INVALID_DATE_RANGE | 2 |
IT_INFOTYPES is empty | hard | NO_INFOTYPES_REQUESTED | 3 |
| Caller's structure doesn't match requested infotypes | hard | STRUCTURE_MISMATCH | 4 |
CX_HRPA_VIOLATED_ASSERTION from READ / READ_WIDE | hard | READ_FAILURE | 5 |
PRELP_TO_PNNNN_TAB assertion | hard | CONVERSION_FAILURE | 6 |
| Mandatory infotype returned empty | hard | MANDATORY_INFOTYPE_EMPTY | 7 |
MISSING_AUTH = 'X' | warning | (appended to ET_MESSAGES, not raised) | n/a |
| Orphan secondary (no owning primary for MOLGA) | warning | (appended to ET_MESSAGES, not raised) | n/a |
Unknown T777D-INFKN value | warning | (appended to ET_MESSAGES, read proceeds as primary) | n/a |
Full definitions of each textid and exc_no are in exception_class_spec.md.
10. Reference — adapted skeleton
This is the internal read_one_infotype helper in pseudo-ABAP, combining everything above. It is not the public API — the public API is in the main class spec. This exists so implementers can see the full flow in one place.
METHOD read_one_infotype.
" IMPORTING iv_pernr iv_infty iv_subty iv_begda iv_endda
" CHANGING ct_typed_tab (STANDARD TABLE OF P<NNNN>)
" Uses instance attributes: mo_infty_reader, mv_molga, mt_prelp_cache,
" mv_sprps, mv_no_auth_check, mt_messages
" 1. Cache hit?
READ TABLE mt_prelp_cache INTO DATA(ls_cached)
WITH TABLE KEY pernr = iv_pernr infty = iv_infty.
IF sy-subrc = 0.
IF ls_cached-t_prelp IS NOT INITIAL.
cl_hr_pnnnn_type_cast=>prelp_to_pnnnn_tab(
EXPORTING prelp_tab = ls_cached-t_prelp
IMPORTING pnnnn_tab = ct_typed_tab ).
IF iv_subty IS NOT INITIAL.
DELETE ct_typed_tab WHERE subty <> iv_subty.
ENDIF.
ENDIF.
RETURN.
ENDIF.
" 2. Classify and resolve linkage
DATA(ls_meta) = get_meta( iv_infty = iv_infty iv_molga = mv_molga ).
CASE ls_meta-infkn.
WHEN 'Z'.
" Secondary: read via owning primary with READ_WIDE
IF ls_meta-owning_primary IS INITIAL.
append_warning( pernr = iv_pernr infty = iv_infty
text = 'Orphan secondary; no owning primary' ).
RETURN.
ENDIF.
do_wide_read( iv_pernr = iv_pernr
iv_primary_infty = ls_meta-owning_primary
iv_begda = iv_begda iv_endda = iv_endda ).
serve_from_cache( iv_pernr iv_infty iv_subty CHANGING ct_typed_tab ).
WHEN 'I'.
IF ls_meta-secondary_infty IS NOT INITIAL.
" Primary with linked secondary: READ_WIDE fills both cache entries
do_wide_read( iv_pernr = iv_pernr
iv_primary_infty = iv_infty
iv_begda = iv_begda iv_endda = iv_endda ).
serve_from_cache( iv_pernr iv_infty iv_subty CHANGING ct_typed_tab ).
ELSE.
" Standalone primary: READ
do_narrow_read( iv_pernr = iv_pernr iv_infty = iv_infty
iv_subty = iv_subty
iv_begda = iv_begda iv_endda = iv_endda
CHANGING ct_typed_tab = ct_typed_tab ).
ENDIF.
WHEN OTHERS.
" Unknown classification: warn and fall back to narrow read
append_warning( pernr = iv_pernr infty = iv_infty
text = |Unknown T777D-INFKN '{ ls_meta-infkn }'; treated as primary| ).
do_narrow_read( iv_pernr = iv_pernr iv_infty = iv_infty
iv_subty = iv_subty
iv_begda = iv_begda iv_endda = iv_endda
CHANGING ct_typed_tab = ct_typed_tab ).
ENDCASE.
ENDMETHOD.
do_wide_read and do_narrow_read mirror §3.1 and §3.2, with the single addition that both insert into mt_prelp_cache on success (and an empty entry on missing_auth to prevent retry).
Two code-level simplifications versus the skeleton above:
- The subtype filter is applied once at the end of
READ_ONE_INFOTYPE(DELETE et_prelp WHERE subty <> iv_subty) regardless of which dispatch branch fired, including the buffer-hit path. Callers therefore see the same post-filter result whether the read hit DB or cache. - The WHEN OTHERS branch emits a warning and falls back to a narrow
READ— it does not re-classify or attempt wide-read recovery.
11. Things that can bite you
- Decoupled infotypes: some infotypes under S/4HANA's decoupled framework have different storage backends.
IF_HRPA_READ_INFOTYPEabstracts this — do not attemptSELECT * FROM PA<NNNN>directly. - Customer infotypes (
9000–9999): often have no T582V/T582W entries. Classification may be blank; fall through to theWHEN OTHERSfallback. - Time infotypes (
2001–2003): return raw records, not evaluated ones. If the caller needs evaluated absence balances, they need a different class (out of scope here). SUBTYfilter onREAD_WIDE: not supported; filter after the cast. Mentioned in §8 but easy to miss.BEGDA > ENDDAat framework level: no defined behavior — validate at the public-method entry point and raiseINVALID_DATE_RANGEbefore any framework call.- Employees transferred between country groupings: if the employee's MOLGA changes within the requested window, the metadata resolution is ambiguous. Current policy: use MOLGA from the
BEGDA-validPA0001record. Document this as a known limitation; revisit if real data requires it.