ParseResult: non-mutating lookup, explicit getOrCreate() and no empty documents emitted - #2112
ParseResult: non-mutating lookup, explicit getOrCreate() and no empty documents emitted#2112abhinav-phi wants to merge 4 commits into
Conversation
…mpty entries ParseResult.get(url) created and stored an empty ParseData on a miss, so a lookup for a URL that was never parsed inserted an entry which the parser bolts emitted as a content-less document (issue apache#2108). - add ParseResult.getIfPresent(url), a read-only lookup returning null on a miss without modifying the ParseResult - add ParseResult.getOrCreate(url) with the creating behaviour, used by put(), set() and the parser bolts; deprecate get() as a delegate so existing parse filters keep working while the intent is explicit at the call site; migrate all in-tree callers - JSoupParserBolt and the Tika ParserBolt skip entries with no content, no text and no metadata when emitting documents - ParseData no-arg and metadata-only constructors set content to an empty byte array, consistent with the (text, metadata) constructor - new ParseResultTest and bolt-level tests verifying that empty entries are not emitted; SubDocumentsParseFilter fixture attaches metadata to the sub-documents it creates
|
Thanks for the PR @abhinav-phi. Compat side looks well handled - deprecated The empty-entry skip is silent - no log, counter or opt-out. URL-only subdocuments as a signal is a real pattern (this PR patched its own fixture for exactly that); should we at least count the skips and add a release-note line for filter authors? Also wondering: could the skip apply only to entries other than the tuple's own URL? Phantoms are by construction subdocuments, and exempting the parent would rule out the corner case where the main document vanishes and the URL keeps refetching. |
Review feedback on apache#2112: - the parser bolts now skip empty entries only for URLs other than the one being parsed: the document itself is always emitted, even when a filter emptied it, so that its status keeps being updated downstream - skips are no longer silent: each one increments a skipped_empty_documents counter and logs at debug level - document the emission semantics for filter authors in internals.adoc - new tests assert that an emptied parent document is still emitted
|
Thanks for the review @dpol1 — both points are addressed in 1c7b442: Skip scoped to sub-documents. The bolts now skip empty entries only for URLs other than the one being parsed. The entry for the document itself is always emitted, even when a filter emptied it, so its status keeps being updated downstream — the corner case you describe is ruled out. Locked in by Skips are no longer silent. Each skip increments a Release note for filter authors. The repo has no in-tree changelog file, so the note lives in the ParseFilter section of All core + tika tests pass (426 core, 4 tika), and the checkstyle / google-java-format / RAT gates are green. |
| * #getOrCreate(String)} to create an entry, so that the intent is explicit at the call site | ||
| * @return An existent instance of Parse for the given URL or an empty one if none can be found | ||
| */ | ||
| @Deprecated |
There was a problem hiding this comment.
the next release will be a major one, we might as well get rid of the method straight away.
There was a problem hiding this comment.
Agreed — since the next release is a major one, get(url) is now removed outright in 8aea8e9 instead of being deprecated. All in-tree callers had already been migrated to getOrCreate(url), and filters that only want to check for an existing entry can use the new getIfPresent(url).
| // emit each document/subdocument in the ParseResult object | ||
| // there should be at least one ParseData item for the "parent" URL | ||
|
|
||
| // there should be at least one ParseData item for the "parent" URL. |
There was a problem hiding this comment.
if we totally deprecate get() - we will probably never come across those empty docs. Happy to keep the check and the log message but probably could get rid of that lengthy AI generated Javadoc
There was a problem hiding this comment.
Kept the check and the log (as a backstop for external filters that still create empty entries via put/set/getOrCreate), plus the skipped_empty_documents counter. The lengthy block comment above the loop is now two lines, and the new accessors' Javadoc has been cut down to the essentials — 8aea8e9.
Review feedback on apache#2112: - since the next release is a major one (4.0.0), remove the ambiguous get(url) method outright instead of deprecating it; filters must use getIfPresent(url) for a read-only lookup or getOrCreate(url) to create an entry - shorten the Javadoc of the new ParseResult accessors and the comments above the emission loops in the parser bolts
JsRenderingDetectorTest holds the ParseResult in a variable named p, which the mechanical migration of parse.get() to getOrCreate() missed. Replace get() with getOrCreate() there as well; verified by a full reactor clean build.
|
The rat failure on 8aea8e9 was a compile error I introduced with the removal of The CI run for 5a44a45 is currently waiting for a maintainer to approve it (workflow runs from first-time contributors require manual approval) — once approved it should go green. |
Fixes #2108
The problem
ParseResult.get(url)is documented as a convenience accessor, but on a miss it creates aParseDataand stores it in the map before returning it.put(url, key, value)andset(url, metadata)go through the same path. Both parser bolts (JSoupParserBoltand the TikaParserBolt) iterate the whole map and emit one document tuple per entry, so an entry created by a lookup is emitted like a parsed document. The auto-created entry also has anullcontent field, while the(text, metadata)constructor sets an empty byte array, so the two paths do not agree on what an empty document looks like.Nothing in the tree hits this today: every non-test caller passes the URL of the page being parsed, so the map only ever gains entries for documents that exist. The problem is the API. A parse filter written against the Javadoc may reasonably call
get()to check whether an earlier filter produced a sub-document; if the URL it probes comes from page content, the probe creates an entry that the bolt then emits as a document. Sub-documents are not filtered by URLFilters (only outlinks are), so that entry reaches the indexer as a document URL with no content.What this PR changes
1.
ParseResultnow separates lookup from creationgetIfPresent(url)— new read-only accessor: returns theParseDatastored for the URL ornull, and never modifies theParseResult. This is what a parse filter should use to probe whether a sub-document already exists.getOrCreate(url)— the creating accessor used byput(),set()and the parser bolts for the parent URL; modifications made to the returned instance are stored in theParseResult.get(url)is removed. Since the next release is 4.0.0, a major one, the ambiguous method is dropped outright rather than deprecated. Filters that usedget(url)to check for a sub-document must switch togetIfPresent(url); those that used it to build one must switch togetOrCreate(url),put(url, key, value)orset(url, metadata).All in-tree callers of
parse.get(url)— the parse filters incore(CommaSeparatedToMultivaluedMetadata,CollectionTagger,DomainParseFilter,LDJsonParseFilter,LinkParseFilter,MD5SignatureParseFilter,MimeTypeNormalization,XPathFilter), thejsoupfilters,LanguageID(langid),JsRenderingDetector(playwright) and both parser bolts — have been migrated togetOrCreate(url). This is a mechanical, behaviour-identical change.2. Parser bolts skip empty sub-documents, never the document itself
JSoupParserBoltand the TikaParserBoltskip entries which carry no content, no text and no metadata when they emit — except the entry for the URL being parsed, which is always emitted even if a filter emptied it, so that its status keeps being updated downstream. Phantoms created by a lookup on a never-parsed URL are by construction sub-documents, so the exemption is scoped to the tuple's own URL.Each skip increments a
skipped_empty_documentscounter (visible in the bolt's metrics) and logs the skipped URL at debug level.3. Consistent
ParseDataconstructorsThe no-arg and metadata-only
ParseDataconstructors now setcontentto an empty byte array, like the(text, metadata)constructor, so no code path can emit anullcontent field.4. Documentation for filter authors
The custom parse filter example in
docs/src/main/asciidoc/extending.adocnow usesgetOrCreate(url), anddocs/src/main/asciidoc/internals.adocdocuments the emission semantics: usegetOrCreate(url)to add data for a URL,getIfPresent(url)for a read-only lookup, and set at least one of content, text or metadata on a sub-document for it to be emitted.Release note for filter authors:
ParseResult.get(url)has been removed — usegetIfPresent(url)for a read-only lookup orgetOrCreate(url)to create an entry. Sub-documents are only emitted if they carry content, text or metadata; entries with none of the three are skipped (counted underskipped_empty_documents) unless they belong to the URL being parsed.Tests
ParseResultTest(new, core): verifies thatgetIfPresent()returnsnullfor an unknown URL without mutating the result and returns the stored instance otherwise; thatgetOrCreate(),put()andset()create entries andgetOrCreate()returns the same instance on repeated calls; thatgetValues()does not mutate the result; and that allParseDataconstructors produce a non-null, empty content array.SubDocumentsFilterTest#testEmptySubDocumentsAreNotEmitted(new, core): a parse filter creates an entry for a URL that was never parsed; theJSoupParserBoltemits only the parent document.SubDocumentsFilterTest#testEmptiedParentDocumentIsStillEmitted(new, core): a filter empties the document itself (no content, no text, no metadata); it is still emitted.ParserBoltTest#testEmptySubDocumentsAreNotEmittedandtestEmptiedParentDocumentIsStillEmitted(new, tika): same two checks for the TikaParserBolt.SubDocumentsParseFilter(test fixture): the sub-documents it creates now carry a metadata entry — since empty sub-documents are no longer emitted, the fixture creates realistic sub-documents and still asserts that they are emitted.Verification
mvn -pl external/tika -am clean test— core 425 tests, 0 failures; tika 4 tests, 0 failures.mvn -pl core,external/tika -Prat verify -DskipTests -Dskip.format.code=false— checkstyle 0 violations, google-java-format validation passed, Apache RAT passed.