Skip to content

add R wrapper for maplib - #74

Draft
novica wants to merge 46 commits into
DataTreehouse:mainfrom
novica:main
Draft

novica wants to merge 46 commits into
DataTreehouse:mainfrom
novica:main

Conversation

@novica

@novica novica commented Sep 16, 2026

Copy link
Copy Markdown

PR has an r language wrapper for maplib. Works on my computer. :) Done with a lot of help from claude code.

See if you want to consider it for this repo.

novica and others added 30 commits September 12, 2026 17:28
- RModel wraps maplib::model::Model behind a Mutex, mirroring py_maplib's PyModel
- new()/size() verified working end-to-end from R
- requires maplib's pyo3 feature and a pinned nightly toolchain

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- ports Model::reads/write_triples for ntriples/turtle/rdf-xml
- verified round-trip from R

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- add create_index/truncate_graph/add_prefixes to RModel
- verified from R

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- copies a graph between RModels, or splits one out into a new RModel
- verified: copy across two models, split empties the source

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- compact() verified working on an in-memory model
- serialize()/deserialize() panic at runtime: lib/disk is a closed-source stub in this checkout, not a port bug

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- ports Datalog RDFS inference
- fix: pyo3::Python::attach panicked under R on any query-materializing call; call Python::initialize() once in RModel::new()
- verified idempotent and that it unblocks infer_rdfs

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- user-facing Model$new()$method(...) API matching py_maplib's call syntax
- mutating methods return self invisibly so calls chain
- requires LD_PRELOAD of libpython for pyo3-ffi symbols to resolve at load

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- DataFrame -> Struct-typed Series -> existing Arrow C Stream plumbing, reused from the Series-level prototype
- rdf_node_types travels as a JSON side-channel keyed by column name
- verified end-to-end from R against a real EagerSolutionMappings fixture

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- IRI, Prefix, Variable, BlankNode, Literal as immutable S7 value objects (Model stays R6, it mutates in place)
- IRI/variable/blank-node syntax validated via oxrdf parsers, not R regex
- remaining OTTR surface (RDFType, Parameter, Argument, Instance, Template, vocab) left for follow-up

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- thin S7 wrappers over savvy objects, built once and never mutated
- bare Variables/terms auto-wrap wherever a Parameter/Argument is expected
- verified: build a multi-parameter template, print real OTTR syntax, instantiate it

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- names match py_maplib exactly, values cross-checked against oxrdf's vocab.rs
- built lazily in .onLoad, since native code isn't registered yet at R-sourcing time

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- thin wrapper around the resolved basic-type IRI (Multi collapses to rdfs:Resource)
- built via plain functions, since S7 class generators refuse `$<-` assignment
- RDFType.Nested() not ported, no current consumer

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- a panic while holding RModel's lock (e.g. serialize() hitting the disk stub) poisoned it, breaking every later call on that instance
- added a lock() helper that ignores poison, routed all 12 call sites through it
- verified: create_index()/reads() succeed after a prior serialize() panic

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- centralizes error conversion via a `[tag] message` prefix, parsed back into a classed R condition
- R code can tryCatch by class (maplibr_argument_error, maplibr_maplib_error)
- verified live for both error kinds

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- covers model basics/graphs, errors, terms/templates/vocab (61 tests)
- infer_rdfs comparison reduced to a smoke test: writes() can't serialize transient triples (maplib-tr6)
- remaining ~25 pytest files need Model$query()/map() first (maplib-l2j)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- covers the malformed-tag fallback and runtime_error branches, unexercised by existing tests

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- streams results through the Arrow C Stream bridge, coalesces multi-typed columns
- fix: category-coded IRI/literal values now decoded via format_native_columns (previously came back as raw codes)
- CONSTRUCT not yet supported; 87 tests passing

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- infer_rdfs() returns a triple count, not a rule count; doc comments corrected
- documented that the multitype coalesce assumes character sub-columns

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- add_template/add_template_string/map/map_no_data backed by a new import_dataframe() Arrow-import helper
- size()/Model$size() gain a graph argument, matching py_maplib

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tion

- a literal "::" substring check misclassified some real IRIs as stOTTR documents
- switch the discriminator to the "{" instance-block brace, never a legal IRI character

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ication

- locks in 85498bd's fix

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- m$add_graph(m), or two wrappers sharing one pointer, double-locked the same non-reentrant Mutex, hanging R permanently
- now compares the raw external pointer behind both wrappers and errors clearly instead

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ck test

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- R6's default clone() only copied the pointer, not the underlying Rust Mutex state, so clones silently shared state
- no cheap deep-copy path exists; detach_graph() remains the supported way to split off an independent Model

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…one()

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Triplestore::write_triples never read graph_transient_triples_map; query() could see transient triples but write()/writes() couldn't
- implemented for NTriples and generic RdfSerializer paths; pretty Turtle errors clearly instead of dropping them
- maplibr's Model$writes() gains the same opt-in argument, default FALSE

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- required changes to shared triplestore/model engine code this package doesn't own
- kept the RDFS-inference test coverage, which relies only on query()'s existing include_transient support

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Title/Authors@R/Description/License were still scaffold placeholders; invalid License failed R CMD check outright
- License set to Apache-2.0, matching the wrapped Rust engine

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… pin

- was hardcoded, could silently drift from the rest of the workspace
- polars itself stays pinned explicitly, since Cargo ignores a member's default-features override on a workspace dep

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…_triples"

- matches py_maplib's default; both still error at runtime since lib/disk is an unimplemented stub

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
novica and others added 16 commits September 13, 2026 21:23
- py_maplib exposes int_ (int is a Python builtin); this package's generator stripped the underscore unnecessarily

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- returns a combined subject/predicate/object data.frame, flattening each pattern's matched rows
- row-varying columns coalesced to one string column rather than cast directly, avoiding silent NA
- returns less type info than SELECT by design

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- a predicate variable can be multi-typed via UNION, not just subject/object, hitting the same silent-NA bug
- routed through the existing flattening path, added a regression test

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- covers reads/writes, SELECT/CONSTRUCT, OTTR mapping, RDFS inference, named graphs, prefixes/indexing, graph viz
- every chunk is a real, executed example
- supersedes an older standalone Quarto demo

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ivate subclass

- public constructor no longer has any way to bypass IRI/Argument validation
- internal callers (Template$instance(), Triple()) use the private subclass instead

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- insert() only accepts CONSTRUCT queries, matching the engine's contract
- bare INSERT DATA/DELETE DATA panics (engine limitation); the WHERE {}-based form works and is documented as the workaround

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- mirrors py_maplib's read(path)/write(path)
- read() guesses format from the file extension, write() defaults to N-Triples

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…le format

- an unrecognized extension hit the engine's panic fallback; this crate builds with panic = "abort", so it took down the whole R session
- guess the format in the R wrapper itself, restricted to supported formats, raising a normal argument error otherwise

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- new RGroundTerm value type restricted to IRI/Literal (blank-node binding has no way to be represented here)
- a bound variable can only be referenced in the query body, not also SELECT-ed as output

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- map_json()/map_json_string() and map_xml()/map_xml_string() apply the Facade-X convention
- map_df() maps a data.frame's columns to triples; map_triples() maps subject/predicate/object columns directly
- all mirror equivalent methods in the existing Python binding

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…p_triples

- map_triples() skipped column-existence validation on empty input, hiding misnamed-column bugs until a later call
- map_df() skipped the root triple the engine always adds, diverging from py_maplib

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- adds examples for read()/write(), update()/insert(), query(bindings=), direct-to-triples mapping
- adds add_udf() and bare INSERT/DELETE DATA to the known-limitations table

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- every Model R6 method gets a standalone model_*() function taking the Model first; purely additive alongside $-methods
- `model_` prefix avoids masking base::write/purrr::map/stats::update
- requires R >= 4.1 for the native pipe; adds passthrough tests

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- rewrites examples to use model_*() + |> instead of R6 $-methods; both styles remain supported
- notes model_add_template()/model_infer_rdfs() return non-Model values, so aren't piped onward

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- limitations are already tracked separately; avoids two lists drifting out of sync

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

1 participant