Skip to content

Commit bba2065

Browse files
authored
Merge pull request #13 from artiz/claude/pdf-parser-code-path
feat(pdf): source code cells from the parser — fully retire pdfium's text path
2 parents 90aca36 + e90e8a0 commit bba2065

5 files changed

Lines changed: 84 additions & 41 deletions

File tree

PDF_CONFORMANCE.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,10 @@ on `lopdf`) that reconstructs each glyph's box from the *font's own advance
8787
widths* and the PDF text/graphics matrices — the same information docling-parse
8888
uses. It is the **default** text layer; set `DOCLING_PDFIUM_TEXT=1` to fall back
8989
to pdfium. Pages without a parseable text layer fall back to pdfium
90-
automatically, so scanned/OCR pages are unaffected. The parser also supplies the
91-
**word cells** TableFormer matches against (`DOCLING_PDFIUM_WORDS` keeps pdfium's);
92-
pdfium still provides page rasters, code cells, and link annotations.
90+
automatically, so scanned/OCR pages are unaffected. The parser supplies **all**
91+
text — prose, the **word cells** TableFormer matches against, and **code cells**
92+
(`DOCLING_PDFIUM_WORDS` reverts words+code to pdfium; `DOCLING_PDFIUM_TEXT`
93+
reverts everything). pdfium now does only page rasterisation + link annotations.
9394

9495
The parser handles Type0/CID + Identity-H and simple Type1/TrueType fonts,
9596
ToUnicode CMaps (`bfchar`/`bfrange`), WinAnsi/MacRoman + `/Differences`
@@ -113,10 +114,13 @@ is a maximal run of glyphs the contraction merges *without* inserting a separato
113114
space, so the per-word segments split at exactly the `delta < gap` points — which
114115
reproduces docling-parse's `word_cells` byte-for-byte (377/377 on 2305-pg9). These
115116
are the per-word tokens TableFormer matches against table-grid cells, replacing
116-
pdfium's word cells (roadmap item 6). Code cells stay on pdfium for now — the
117-
parser's space-glyph-only code grouping drops monospace spacing
118-
(`function add``functionadd`); opt into the parser code path with
119-
`DOCLING_PARSER_CODE` once that's fixed.
117+
pdfium's word cells (roadmap item 6). **Code cells** come from the parser too,
118+
via a gap-based grouping (`Grouping::CodeGap`): the parser emits no space glyphs
119+
(a source space is a positioning gap), so a word breaks wherever the inter-glyph
120+
gap exceeds ~0.25× the line height, with no punctuation glue — `et al. 2000`
121+
keeps its space while `add(a,` / `b)` stay joined. `code_and_formula` is byte-exact
122+
(`function add(a, b) { return a + b; }`). With this, pdfium's text path is fully
123+
retired (rasters + links only).
120124

121125
Other text/serializer/layout fixes matching docling: markdown escaping (`_``\_`,
122126
then HTML-escape `&`/`<`/`>`), typographic-punctuation normalization

PDF_PARSER_NOTES.md

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,10 @@ numbering and footnote reading order.)
163163
4. ~~Korean quote normalization~~ — DONE (normal_4pages 74→54).
164164
5. **Now: 6/14 strict, 7/14 whitespace-normalized.** Blocker B (amt) needs a
165165
font-metrics layer for strict 7/14.
166-
6. **Word cells off pdfium — DONE (parser is now the default word source).** The
167-
parser is already the sole *prose* source (item 2); **word** cells now come
168-
from it too. The insight: docling-parse's `word_cells` are exactly the line
166+
6. **Drop pdfium's text path — DONE (parser is the sole text source).** The
167+
parser was already the sole *prose* source (item 2); **word** and **code**
168+
cells now come from it too, so pdfium does only rasterisation + links.
169+
*Word cells:* the insight is that docling-parse's `word_cells` are exactly the line
169170
contraction's runs, split at the points where `merge_with` inserts a separator
170171
space (`delta < gap`). So instead of the legacy gap-heuristic
171172
(`words_from_glyphs`, which blob-joined TJ-spaced runs like
@@ -184,12 +185,25 @@ numbering and footnote reading order.)
184185
the now docling-faithful output (e.g. `bold ,` / `x 2`, which docling-parse's
185186
own `word_cells` confirm — the old `bold,` / `x2` were pdfium punct-gluing).
186187

187-
**Code cells still pdfium.** The parser's space-glyph-only code grouping drops
188-
the inter-token spaces pdfium recovers in monospace listings
189-
(`function add``functionadd`, a regression vs groundtruth), so code cells
190-
stay on pdfium, gated behind `DOCLING_PARSER_CODE` for a future fix (the parser
191-
needs faithful monospace spacing before the *code* path can retire too). pdfium
192-
thus remains for code cells + page rasterisation + link annotations.
188+
**Code cells off pdfium too — DONE (pdfium's text path fully retired).** The
189+
first attempt at parser code cells used the space-glyph-only grouping, which
190+
dropped the inter-token spaces pdfium recovers (`function add``functionadd`)
191+
because the parser emits no space glyphs — a source space is a positioning
192+
*gap*. The fix is a third grouping mode, `Grouping::CodeGap`: split on the
193+
inter-glyph gap (a space wherever it exceeds ~0.25× the line height) but with
194+
**no punctuation glue**, so a real gap always splits (`et al. 2000`, not
195+
`et al.2000` — the prose glue rule is wrong for code) while genuinely touching
196+
tokens stay joined (`add(a,` / `b)`). The parser's clean advance boxes make the
197+
gap reliable here, where pdfium's overhanging loose boxes would over-split
198+
(`f un c t i o n`) — which is why pdfium keeps the space-glyph path.
199+
`code_and_formula` is byte-exact (`function add(a, b) { return a + b; }
200+
console.log(add(3, 5));`); it's the default (opt out with `DOCLING_PDFIUM_WORDS`
201+
or `DOCLING_PDFIUM_TEXT`). Two snapshots (`2305` llncsdoc, `redp5110`) drift in
202+
garbled multi-column LaTeX/SQL "code" regions — conformance-**neutral** vs the
203+
groundtruth (337/337, 175/175), regenerated to the parser output.
204+
205+
pdfium now does **only** page rasterisation + link annotations; all text
206+
(prose, words, code) comes from the pure-Rust parser.
193207

194208
## Tooling (under `scripts/`)
195209

crates/fleischwolf-pdf/src/pdfium_backend.rs

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,15 @@ pub(crate) fn use_parser_words() -> bool {
8686
std::env::var("DOCLING_PDFIUM_WORDS").is_err() && std::env::var("DOCLING_PDFIUM_TEXT").is_err()
8787
}
8888

89-
/// Whether to source **code** cells from the parser too. Off by default: the
90-
/// parser's space-glyph-only code grouping drops the inter-token spaces pdfium
91-
/// recovers in monospace listings (`function add` → `functionadd`), a regression
92-
/// vs the docling groundtruth. Opt in with `DOCLING_PARSER_CODE` once the parser
93-
/// emits faithful monospace spacing. `DOCLING_PDFIUM_TEXT` always wins (pdfium).
89+
/// Whether to source **code** cells from the parser too (the default) — the last
90+
/// text layer to leave pdfium, fully retiring its text path. The parser's
91+
/// gap-based code grouping ([`code_cells_from_glyphs`]) reconstructs monospace
92+
/// spacing from positioning gaps (`function add(a, b) { … }`), so it no longer
93+
/// drops the inter-token spaces the old space-glyph-only grouping lost
94+
/// (`functionadd`). Reverts to pdfium with `DOCLING_PDFIUM_WORDS` (alongside word
95+
/// cells) or `DOCLING_PDFIUM_TEXT` (all text).
9496
pub(crate) fn use_parser_code() -> bool {
95-
std::env::var("DOCLING_PARSER_CODE").is_ok() && std::env::var("DOCLING_PDFIUM_TEXT").is_err()
97+
std::env::var("DOCLING_PDFIUM_WORDS").is_err() && std::env::var("DOCLING_PDFIUM_TEXT").is_err()
9698
}
9799

98100
fn bind() -> Result<Pdfium, PdfiumError> {
@@ -355,11 +357,11 @@ impl<'a> FfiText<'a> {
355357
let prose = if dp {
356358
crate::dp_lines::line_cells(&g, page_h, false)
357359
} else {
358-
lines_from_glyphs(&g, page_h, false)
360+
lines_from_glyphs(&g, page_h, Grouping::Prose)
359361
};
360362
(
361363
prose,
362-
lines_from_glyphs(&g, page_h, true),
364+
lines_from_glyphs(&g, page_h, Grouping::CodeSpaceOnly),
363365
words_from_glyphs(&g, page_h),
364366
)
365367
};
@@ -548,18 +550,33 @@ fn glyphs(b: &dyn PdfiumLibraryBindings, tp: FPDF_TEXTPAGE, fetch_font: bool) ->
548550
out
549551
}
550552

553+
/// How [`lines_from_glyphs`] splits a line into words.
554+
#[derive(Clone, Copy, PartialEq)]
555+
enum Grouping {
556+
/// Gap heuristic + punctuation glue (`engines,`, `[37`, `98.5`) — prose.
557+
Prose,
558+
/// Split only at literal space glyphs, never glue — pdfium code cells.
559+
/// pdfium's monospace listings carry a real space glyph at every source space,
560+
/// and its overhanging loose boxes would make the gap heuristic over-split
561+
/// (`f un c t i o n`), so honouring just the spaces reproduces the spacing.
562+
CodeSpaceOnly,
563+
/// Split on the inter-glyph **gap** (or a space glyph), but never glue — for
564+
/// the parser's code cells: the parser emits no space glyphs (a source space
565+
/// is a positioning gap), and its clean advance boxes make the gap reliable.
566+
/// Unlike [`Grouping::Prose`] there is no punctuation glue, so a real gap
567+
/// always splits (`et al. 2000`, not `et al.2000`) while genuinely touching
568+
/// tokens stay joined (`add(a,` / `b)`).
569+
CodeGap,
570+
}
571+
551572
/// Group glyphs (document order) into words then lines, the way docling-parse
552573
/// does: a new **word** starts where the horizontal gap to the previous glyph
553574
/// exceeds ~0.2 × the font height (a real space is ~0.3 × height; letter
554575
/// tracking is smaller, so titles don't shatter); a new **line** starts where
555576
/// the baseline drops by ~half the font height (a superscript rises without
556577
/// dropping, so it stays on its line). Coordinates are flipped to top-left.
557-
/// `code` mode splits words **only** at pdfium's own space glyphs and never glues
558-
/// punctuation — monospace code has wide inter-glyph advances that the prose
559-
/// gap heuristic mistakes for spaces (`f un c t i o n`), but pdfium emits a real
560-
/// space glyph at every true gap, so honoring just those reproduces the source
561-
/// spacing (`function add(a, b)`).
562-
fn lines_from_glyphs(gs: &[Glyph], page_h: f32, code: bool) -> Vec<TextCell> {
578+
/// See [`Grouping`] for how each mode decides word boundaries.
579+
fn lines_from_glyphs(gs: &[Glyph], page_h: f32, mode: Grouping) -> Vec<TextCell> {
563580
let mut cells: Vec<TextCell> = Vec::new();
564581
let mut words: Vec<String> = Vec::new(); // words on the current line
565582
let mut word = String::new();
@@ -613,8 +630,11 @@ fn lines_from_glyphs(gs: &[Glyph], page_h: f32, code: bool) -> Vec<TextCell> {
613630
&& !pending_space
614631
&& (g.ch.is_ascii_digit() || g.ch.is_ascii_lowercase()));
615632
let word_gap = line_h.max(h) * 0.25;
616-
new_word = if code {
633+
new_word = if mode == Grouping::CodeSpaceOnly {
617634
new_line || pending_space
635+
} else if mode == Grouping::CodeGap {
636+
// Gap-based, no glue: a real gap always splits, touching tokens join.
637+
new_line || pending_space || g.l - p.r > word_gap
618638
} else if is_arabic(g.ch) || is_arabic(p.ch) {
619639
// RTL runs right-to-left, so the inter-word gap is `p.l - g.r`. A
620640
// real word space has a gap; pdfium also emits spurious zero-gap
@@ -652,11 +672,16 @@ fn lines_from_glyphs(gs: &[Glyph], page_h: f32, code: bool) -> Vec<TextCell> {
652672
cells
653673
}
654674

655-
/// Code line cells from a glyph stream (parser or pdfium): split only at space
656-
/// glyphs so monospace runs keep their source spacing. Thin wrapper over
657-
/// [`lines_from_glyphs`] with `code = true`, for the parser text path.
675+
/// Code line cells from the **parser**'s glyph stream. Unlike pdfium — whose
676+
/// monospace listings carry explicit space glyphs (so [`Grouping::CodeSpaceOnly`]
677+
/// keeps their spacing) — the parser emits no space glyphs: a source space is a
678+
/// positioning gap. So code cells use [`Grouping::CodeGap`], which splits on the
679+
/// inter-glyph gap (a space wherever it exceeds ~0.25× the line height) but never
680+
/// glues punctuation, so `et al. 2000` keeps its space while `add(a,` / `b)` stay
681+
/// joined. The parser's clean advance boxes make the gap heuristic reliable here,
682+
/// where pdfium's overhanging loose boxes would over-split (`f un c t i o n`).
658683
pub(crate) fn code_cells_from_glyphs(gs: &[Glyph], page_h: f32) -> Vec<TextCell> {
659-
lines_from_glyphs(gs, page_h, true)
684+
lines_from_glyphs(gs, page_h, Grouping::CodeGap)
660685
}
661686

662687
/// Per-word cells (each word's text + top-left bbox), using the same word/line

tests/snapshots/latex/sources/2305.03393/llncsdoc.pdf.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ Multiple affiliations are separated by \and , which automatically assures correc
113113
\and
114114

115115
```
116-
\institute{<name of an institute> \and <name of the next institute> \and <name of the next institute>} \email Inside \institute you can use \email{<email address>} \url and \url{<url>}
116+
\institute{<name of an institute> \and <name of the next institute> \and <name of the next institute>} Inside \institute you can use\email \email{<email address>} and\url \url{<url>}
117117
```
118118

119119
```
@@ -143,7 +143,7 @@ Please note that, if email addresses are given in your paper, they will also be
143143
abstract ( env. ) The abstract is coded as follows:
144144

145145
```
146-
abstract(env.) The abstract is coded as follows: \begin{abstract} <Text of the summary of your paper> \end{abstract}
146+
The abstract is coded as follows:abstract(env.) \begin{abstract} <Text of the summary of your paper> \end{abstract}
147147
```
148148

149149
```
@@ -167,7 +167,7 @@ From a technical point of view, the llncs document class does not require any sp
167167
The llncs document class supports some additional special characters:
168168

169169
```
170-
\grole yields > < \getsto yields ← → \lid yields < = \gid yields > =
170+
\grole yields > \lid yields < < \getsto yields ←→ = \gid yields > =
171171
```
172172

173173
If you need blackboard bold characters, i.e. for sets of numbers, please load the related A M S -T E Xfonts. If for some reason this is not possible you can also use the following commands from the llncs class:
@@ -200,7 +200,7 @@ proposition ( env. )
200200
example ( env. )
201201

202202
```
203-
property(env.) question(env.) exercise(env.) solution(env.)heading is bold as well: problem(env.) note(env.) \begin{case} <text> \end{case} \begin{conjecture} <text> \end{conjecture} \begin{example} <text> \end{example} \begin{exercise} <text> \end{exercise} \begin{note} <text> \end{note} \begin{problem} <text> \end{problem} remark(env.) \begin{property} <text> \end{property} \begin{question} <text> \end{question} \begin{remark} <text> \end{remark} \begin{solution} <text> \end{solution}
203+
exercise(env.) property(env.) question(env.) solution(env.) heading is bold as well: problem(env.) note(env.) \begin{case} <text> \end{case} \begin{conjecture} <text> \end{conjecture} \begin{example} <text> \end{example} \begin{exercise} <text> \end{exercise} \begin{note} <text> \end{note} \begin{problem} <text> \end{problem} remark(env.) \begin{property} <text> \end{property} \begin{question} <text> \end{question} \begin{remark} <text> \end{remark} \begin{solution} <text> \end{solution}
204204
```
205205

206206
claim ( env. ) Finally, there are also two unnumbered environments that have the run-in headproof ( env. ) ing in italics and the text in upright roman.
@@ -260,7 +260,7 @@ citeauthoryear
260260
Please note that this option does not automatically change your citations to the author/year style. It basically redefines the \bibitem command to take the publication year as an optional parameter that is displayed instead of an arabic number. Author name(s) and, if necessary, parentheses are to be typed manually. If your reference reads
261261

262262
```
263-
\bibitem[2016]{vdaalst:2016} van der Aalst, W.: Process Mining, 2nd ed. Springer, Heidelberg(2016) and is cited as follows:... is shown by van der Aalst(\cite{vdaalst:2016}) the resulting text will be: "... is shown by van der Aalst(2016)."
263+
\bibitem[2016]{vdaalst:2016} van der Aalst, W.: Process Mining, 2nd ed. Springer, Heidelberg(2016) and is cited as follows:... is shown by van der Aalst(\cite{vdaalst:2016}) the resulting text will be: "...is shown by van der Aalst(2016)."
264264
```
265265

266266
We encourage you to use Bib T E X for typesetting your references. For formatting the bibliography according to Springer's standard (for mathematics, physical sciences, and computer science), please use the bibliography style file splncs04.bst that comes with the llncs document class. You simply need to add \bibliographystyle{splncs04} to your document. DOIs should be provided in the doi field of your .bib database. Bib T E X will then automatically add them to your references. Please note that we do not provide an option to implement splncs04.bst

tests/snapshots/pdf/sources/redp5110_sampled.pdf.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ Figure 4-69 Index advice with no RCAC
336336
<!-- image -->
337337

338338
```
339-
WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'TELLER') = 1 WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'CUSTOMER') = 1 WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'ADMIN') = 1 WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'TELLER') = 1 WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'CUSTOMER') = 1 124 Row and Column Access Control Support in IBM DB2 for iTHEN C. CUSTOMER_TAX_ID THEN( 'XXX-XX-' CONCAT QSYS2. SUBSTR( C. CUSTOMER_TAX_ID, 8, 4)) THEN C. CUSTOMER_TAX_ID THEN C. CUSTOMER_DRIVERS_LICENSE_NUMBER THEN C. CUSTOMER_DRIVERS_LICENSE_NUMBER THEN C. CUSTOMER_DRIVERS_LICENSE_NUMBER CREATE MASK BANK_SCHEMA.MASK_DRIVERS_LICENSE_ON_CUSTOMERS ON BANK_SCHEMA.CUSTOMERS AS C CREATE MASK BANK_SCHEMA.MASK_LOGIN_ID_ON_CUSTOMERS ON BANK_SCHEMA.CUSTOMERS AS C ELSE 'XXX-XX-XXXX' END ELSE '*************' END RETURN CASE RETURN CASE ENABLE; FOR COLUMN CUSTOMER_DRIVERS_LICENSE_NUMBER ENABLE; FOR COLUMN CUSTOMER_LOGIN_ID ALTER TABLE BANK_SCHEMA.CUSTOMERS WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'ADMIN') = 1 WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'CUSTOMER') = 1 WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'ADMIN') = 1 WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'CUSTOMER') = 1 WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'ADMIN') = 1 WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'CUSTOMER') = 1 THEN C. CUSTOMER_LOGIN_ID THEN C. CUSTOMER_LOGIN_ID THEN C. CUSTOMER_SECURITY_QUESTION THEN C. CUSTOMER_SECURITY_QUESTION THEN C. CUSTOMER_SECURITY_QUESTION_ANSWER THEN C. CUSTOMER_SECURITY_QUESTION_ANSWER CREATE MASK BANK_SCHEMA.MASK_SECURITY_QUESTION_ON_CUSTOMERS ON BANK_SCHEMA.CUSTOMERS AS C CREATE MASK BANK_SCHEMA.MASK_SECURITY_QUESTION_ANSWER_ON_CUSTOMERS ON BANK_SCHEMA.CUSTOMERS AS C ELSE '*****' END ELSE '*****' END ELSE '*****' END ACTIVATE ROW ACCESS CONTROL ACTIVATE COLUMN ACCESS CONTROL; RETURN CASE RETURN CASE ENABLE; FOR COLUMN CUSTOMER_SECURITY_QUESTION ENABLE; FOR COLUMN CUSTOMER_SECURITY_QUESTION_ANSWER ENABLE;
339+
124 Row and Column Access Control Support in IBM DB2 for iTHEN C. CUSTOMER_TAX_ID WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'TELLER') = 1 THEN( 'XXX-XX-' CONCAT QSYS2. SUBSTR( C. CUSTOMER_TAX_ID, 8, 4)) WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'CUSTOMER') = 1 THEN C. CUSTOMER_TAX_ID ELSE 'XXX-XX-XXXX' END ENABLE; CREATE MASK BANK_SCHEMA.MASK_DRIVERS_LICENSE_ON_CUSTOMERS ON BANK_SCHEMA.CUSTOMERS AS C WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'ADMIN') = 1 THEN C. CUSTOMER_DRIVERS_LICENSE_NUMBER WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'TELLER') = 1 THEN C. CUSTOMER_DRIVERS_LICENSE_NUMBER WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'CUSTOMER') = 1 THEN C. CUSTOMER_DRIVERS_LICENSE_NUMBER ELSE '*************' END ENABLE; CREATE MASK BANK_SCHEMA.MASK_LOGIN_ID_ON_CUSTOMERS ON BANK_SCHEMA.CUSTOMERS AS C FOR COLUMN CUSTOMER_DRIVERS_LICENSE_NUMBER RETURN CASE FOR COLUMN CUSTOMER_LOGIN_ID RETURN CASE WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'ADMIN') = 1 THEN C. CUSTOMER_LOGIN_ID WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'CUSTOMER') = 1 THEN C. CUSTOMER_LOGIN_ID ELSE '*****' END ENABLE; CREATE MASK BANK_SCHEMA.MASK_SECURITY_QUESTION_ON_CUSTOMERS ON BANK_SCHEMA.CUSTOMERS AS C WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'ADMIN') = 1 THEN C. CUSTOMER_SECURITY_QUESTION WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'CUSTOMER') = 1 THEN C. CUSTOMER_SECURITY_QUESTION ELSE '*****' END ENABLE; CREATE MASK BANK_SCHEMA.MASK_SECURITY_QUESTION_ANSWER_ON_CUSTOMERS ON BANK_SCHEMA.CUSTOMERS AS C WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'ADMIN') = 1 THEN C. CUSTOMER_SECURITY_QUESTION_ANSWER WHEN QSYS2. VERIFY_GROUP_FOR_USER( SESSION_USER, 'CUSTOMER') = 1 THEN C. CUSTOMER_SECURITY_QUESTION_ANSWER ELSE '*****' END ENABLE; ALTER TABLE BANK_SCHEMA.CUSTOMERS FOR COLUMN CUSTOMER_SECURITY_QUESTION RETURN CASE FOR COLUMN CUSTOMER_SECURITY_QUESTION_ANSWER RETURN CASE ACTIVATE ROW ACCESS CONTROL ACTIVATE COLUMN ACCESS CONTROL;
340340
```
341341

342342
<!-- image -->

0 commit comments

Comments
 (0)