The Persistence Wurblet API — org.tentackle.persist.wurblet Class Reference¶
Overview¶
persistence-wurblets.md describes what the persistence wurblets generate. This
document describes the Java classes behind them: the package org.tentackle.persist.wurblet, shipped in
tentackle-persistence-wurblets.
It is the implementation-side counterpart of
The Wurblet API, which documents
org.tentackle.wurblet — the base classes, the argument mini-language and the join model this package builds on.
Read that one first; almost everything here consumes it.
Where the interface-side package parses a model and a query expression, this package turns that parsed expression into executable code. Six classes do the work:
DbModelWurblet— the base class of every query wurblet, and by far the largest: it owns the parsed query, the tracking/context options, and roughly forty helper methods that emit SQL fragments, JDBC parameter binding, method signatures and relation-navigation code.WhereClauseGeneratorandJoinClauseGenerator— the twoCodeGeneratorimplementations that walk the argument expression tree; the first emits Java code that builds SQL at runtime, the second emits literal SQL.DslDeclarationFactory— generates the query-DSL table and field constants.RemoteIncludesandRemoteMethodHelper— the TRIP remote-delegate machinery.
Like its sibling this is a build-time only module, packaged with an Automatic-Module-Name of
org.tentackle.persist.wurblet. It runs on the wurbelizer-maven-plugin's classpath and never ships inside the
running application.
Who consumes these classes¶
| Class | Used by |
|---|---|
DbModelWurblet |
every template in this module except QueryDSL, via header.incl's @{extends DbModelWurblet} |
WhereClauseGenerator |
genwhere.incl — and thus every select/update/delete/reference wurblet |
JoinClauseGenerator |
DbModelWurblet itself, when a load join carries a filter |
DslDeclarationFactory |
QueryDSL.wrbl |
RemoteIncludes |
AssertRemote, RemoteMethod, MethodsImpl, PdoCache and all ten Db…/Pdo… query wurblets |
RemoteMethodHelper |
RemoteMethod.wrbl |
Package Map¶
org.tentackle.wurblet.ModelWurblet (tentackle-wurblets)
└── DbModelWurblet .......................... base of every query wurblet
│
├── owns ──► WurbletArgumentParser (the parsed query: expression, sorting,
│ extra args, join paths — normalized)
│
├── delegates ──► WhereClauseGenerator ─── implements CodeGenerator<Object>
│ │ emits JAVA that appends SQL to a StringBuilder at runtime
│ └── uses ComponentInfo for the EXISTS clause of component paths
│
└── delegates ──► JoinClauseGenerator ─── implements CodeGenerator<Object>
emits LITERAL SQL for the filter of a load join
independent helpers
───────────────────
DslDeclarationFactory ── record Declaration(comment, code) → Table/Field constants
RemoteIncludes ──────── HeapStreams + FreeMarker → TRIP delegate files
RemoteMethodHelper ──── wraps RemoteMethodInfo → remote method signatures
| Class | Kind | Role |
|---|---|---|
DbModelWurblet |
class | Base for all persistence wurblets: query model, tracking/context options, SQL/JDBC/method code builders. |
WhereClauseGenerator |
class | Emits the Java that assembles the SQL WHERE clause, including EXISTS subqueries for relation paths. |
JoinClauseGenerator |
class | Emits the literal SQL for a filtered load join. |
DslDeclarationFactory |
class | Builds the query-DSL Table/Field constant declarations from the model. |
RemoteIncludes |
class | Creates the TRIP remote interface/implementation files from FreeMarker templates and hands out the streams the wurblets write their remote methods into. |
RemoteMethodHelper |
class | Wraps the analyze-time RemoteMethodInfo of an @RemoteMethod and builds its declaration/invocation parameter strings. |
DbModelWurblet¶
DbModelWurblet extends
ModelWurblet, so everything
documented there — model loading, target resolution, getOption, the naming and comment helpers,
assertSupportedByBackends — is available here too. This section covers only what it adds.
Configuration flags¶
Two @{config …}@ flags in the template control how the arguments are parsed:
| Flag | Effect |
|---|---|
groupArgs |
The \| character separates the WHERE expression from a second group of arguments (getExtraArguments()). Used by DbUpdateBy/PdoUpdateBy for the SET values. Without it, \| only ends the expression and improves readability. |
pathAllowed |
Expression arguments may follow relation paths to other entities. Without it, any path argument is rejected outright. |
The two are read in an
if/else ifchain, so a template declaring both only getsgroupArgs. No template in the reactor currently needs both, but it is a trap if one ever does.
Lifecycle: what run() adds¶
run()
├─ 1. read the config flags (groupArgs / pathAllowed) — before super.run()
├─ 2. super.run() (ModelWurblet: load the model, resolve the entity)
└─ 3. if the entity was resolved:
├─ seed the tracking flags from the entity's TrackType and the context attribute
│ from getContextIdAttribute()
├─ apply the option overrides: --tracked / --attracked / --fulltracked / --untracked /
│ --context=<key>
├─ build the WurbletArgumentParser over the positional arguments
├─ reject relation paths unless pathAllowed
├─ reject optional conditions (?boolean) inside join filters — they are supported
│ only in the WHERE expression
└─ if remoting is on: reject joined relations that are neither composite, nor
serialized, nor EAGER (they could not be transported), and apply the same
check to their opposite relations
Everything is skipped when the entity is null, which happens only under missingModelOk.
Options¶
| Option | Accessor | Meaning |
|---|---|---|
--tracked |
isTracked() |
Return a TrackedList instead of a plain List, even if the model does not say [TRACKED]. |
--attracked |
isAttracked() |
As --tracked, plus per-attribute is…Modified(). |
--fulltracked |
isFullTracked() |
As --attracked, plus the last persisted values are kept. |
--untracked |
— | Disable whatever the model declared. |
--context=<key> |
getContextAttribute() |
The domain-context column. --context= with an empty value turns the default context predicate off. |
The three predicates nest: fullTracked implies attracked implies tracked.
Guards¶
| Method | Rejects |
|---|---|
isEntityPersistable() / assertEntityIsPersistable() |
An abstract entity whose hierarchy maps to no table — nothing to run SQL against. |
assertEntityNotEmbedded() |
An embedded entity, naming the entities it is embedded in. It has no table of its own to select from. |
assertNoOptionalArguments() |
Any ?boolean condition. Called by the wurblets that cache their prepared statement, since an optional condition makes the SQL text variable. |
The parsed query¶
| Method | Returns |
|---|---|
getExpression() |
The WurbletArgumentExpression tree for the WHERE clause. |
getExpressionArguments() |
The flat list of arguments inside that tree. |
getExtraArguments() |
The arguments after \| — the SET list of an update. |
getMethodArguments() |
Join-filter arguments first, then the parser's own method arguments. This ordering is the JDBC parameter binding order, so it must match the order in which the SQL is assembled. |
getSortingArguments() / isWithSorting() |
The explicit +/- sort keys. |
getDefaultSorting() / getDefaultSortKeys() / isWithDefaultSorting() |
The model's default sorting. getDefaultSorting() walks up the inheritance tree until it finds an entity that declares one; getDefaultSortKeys() re-parses those into WurbletArguments. |
getJoinPaths() / isWithJoins() |
The consolidated load-join paths — normalized, so every node holds exactly one Join. All the generation code relies on this and simply reads getElements().get(0). |
isWithFilteredJoins(componentsOnly) |
Whether any join carries a filter, i.e. whether the result must be made immutable. |
isWithOptionalArguments() |
Whether any condition is optional, which implies --oneshot. |
createOptionalCondition(args) |
The Java boolean expression deciding whether a group of arguments is part of the query: the \|\| of their distinct boolean names, or null as soon as one argument is mandatory (the group is then always emitted). |
isPdoProvidingArguments() |
Whether some fixed value is a method call such as getBlah() rather than a literal. |
isAbstractJoinPath(paths) |
Whether any join touches an abstract entity — the generated casts are then unchecked and need a @SuppressWarnings. |
SQL builders¶
createRelopCode(arg) and createRelopSql(arg) are the same switch over the relational operator, rendered
two different ways. Both first verify the array operator against every active backend via
assertSupportedByBackends, and both quote a literal value that is not already quoted.
createRelopCode |
createRelopSql |
|
|---|---|---|
| Produces | Java: .append(Backend.SQL_EQUAL_PAR) |
SQL: =? |
| Consumed by | WhereClauseGenerator (runtime assembly) |
JoinClauseGenerator (fixed join filter text) |
The switch maps =, <>/!=, <, >, <=, >=, LIKE, NOT LIKE, IS NULL and IS NOT NULL onto the
Backend.SQL_* constants, choosing the …_PAR variant (which carries the ?) unless the value is literal or the
argument is an array. An unrecognized relop falls through to the raw text plus ?. Array arguments then get the
matching SQL_ARRAY_{ANY,ALL,IN,NOT_IN}_PAR suffix appended.
createOrderBy(sortKeys) / createOrderBy() build the ORDER BY list, expanding a multi-column datatype
into its sortable columns (and failing with "… is not sortable by the database" if it has none). Column
qualification depends on where the attribute lives:
- multi-table inheritance → qualify with the top super-entity's
CLASSVARIABLES; - an attribute of another entity → find the load join that brought it in and emit the literal
"j_1.column", honoring the embedding column prefix; if no join matches and the attribute is not reachable through an embedding path, the build fails with "missing join for sort key"; - an attribute of this entity →
getColumnName(CN_…)for PDOs, the bare constant otherwise.
createJoins() emits the whole JoinedSelect<T> construction for eager loading — a nested chain of
addJoin(new Join<>(JoinType.LEFT, …)) calls, one per normalized path node, each with a lambda that links the
loaded row into its parent. List and object relations differ:
| List relation | Object relation | |
|---|---|---|
| Join columns | parent id → child foreign attribute |
parent foreign key → child id |
| Linking code | getXBlunt().addBlunt(child), or the setter when the relation is reversed; a link method is invoked when the model declares one |
the setter on the child's persistence delegate, walking the embedding getter path for embedded entities |
Blunt is appended to the setter name when the relation is serialized. createOptionalWhereForJoin adds the
extra predicates for relations with more than one method argument (a value becomes =?, otherwise the columns are
compared) and finally appends the join's own filter SQL produced by JoinClauseGenerator.
JDBC parameter binding¶
| Method | Emits |
|---|---|
createWhereSetPars(WurbletArgument) |
The binding for one condition. Arrays become st.setArray(ndx++, Element.class, columnIndex, value, Backend.SQL_ARRAY_…). With an explicit column index, a predefined multi-column type (BMoney, OffsetDateTime, …) binds one column via st.set(SqlType.…, ndx++, …), while an application type uses ndx += st.set(DATATYPE, ndx, value, columnIndex, mapNull, size). Without a column index it delegates to the attribute form below. |
createWhereSetPars(Attribute, String argument) |
The single-column form: st.set<Type>(ndx++, code) for predefined types (with a trailing ndx advance when the type spans several columns), ndx += st.set(DATATYPE, ndx, code, mapNull, size) otherwise. Rejects backend-specific column counts in a WHERE clause. |
createJoinSetPars() |
Binds the value method arguments of every join, descending recursively into the sub-paths — in the same order createJoins() emitted them. |
createStatementId() turns the wurblet's guard name into the UPPER_SNAKE_CASE name of the cached statement id,
suffixed _STMT (selectByName → SELECT_BY_NAME_STMT).
Method signatures¶
buildMethodParameters(limit, offset) and buildInvocationParameters(limit, offset) produce the declaration and
the call site of the generated method, so they must stay in lockstep. Both walk getMethodArguments(),
deduplicating by argument name, and both append a boolean <name> parameter for each distinct optional
condition. An array argument is declared as Collection<Element> — the element type being the application type
for convertibles and the boxed type otherwise. The no-argument
overloads pass false, false.
Four one-liners keep the templates readable: acs(str, s) / pcs(str, s) append/prepend to a comma-separated
list, aas(str) prefixes a non-empty string with ", ", and as(str) maps null to the empty string.
DataType and convertible handling¶
| Method | Purpose |
|---|---|
createJdbcSetterName(DataType) |
The PreparedStatementWrapper setter: set + the Java type, with setLargeString for the large String variant. |
createJdbcGetterName(Attribute) |
The ResultSetWrapper getter. Nullable wrapper types get an A prefix — getABoolean, getAByte, getAShort, getALong, getAFloat, getADouble — to distinguish them from the primitive getters; the large String variant becomes getLargeString. |
getModelCode(attr, jdbcCode) |
Wraps JDBC-side code in <Type>.toInternal(…) for a convertible attribute; returns it unchanged otherwise. |
getJdbcCode(attr, modelCode) |
The inverse: appends .toExternal(). An enum-style constant is converted directly; otherwise a null guard is generated — falling back to getDefault().toExternal() when the inner type is primitive, and to null when it is nullable. |
Relation navigation code¶
These build the code PdoRelations and ClassVariables weave into the persistence implementation.
| Method | Produces |
|---|---|
isRelationTransient(Relation) |
Whether the field needs the transient modifier — true for lazy, non-composite, non-serialized relations. |
createRelationArgString / createRelationWurbletArgString |
The (a, b) invocation argument list, and the space-separated foreign attribute names used to build a nested wurblet anchor. |
createRelationSelectCode(Relation) |
on(Foo.class).selectBy…(args), prefixed with a (Foo<?>) cast when the foreign entity is abstract and the framework's own select/selectCached is used — without it the generic type would not match. |
createRelationDeleteCode / createRelationLinkCode |
The bulk-delete call and the back-reference setter (honoring an explicit link method and its optional index argument). |
createRelationUpdateReferenceCode(relation, pdo, blunt) |
The code that writes the back-reference. The blunt request is silently downgraded unless the opposite relation is a serialized object relation with lazy or eager selection; a declared link method wins over the setter when not blunt. The one-argument overload fixes the PDO to me() and never uses blunt. |
createRelationSetFirstArgMethodName / getFirstMethodAttribute |
The setter for a single-argument relation; both raise a ModelException on a relation whose method arguments do not fit. |
getEagerRelations() |
The relations eligible for the persistence layer's optimized single-join eager select. |
WhereClauseGenerator¶
WhereClauseGenerator implements
CodeGenerator<Object> and is handed to WurbletArgumentExpression.toCode(…). It does not produce SQL — it
produces the Java source that appends SQL to a StringBuilder named sql at runtime, which is what allows a
generated finder to omit optional conditions and still reuse the same code path.
// for the argument processed:>=
sql.append(getColumnName(CN_PROCESSED));
sql.append(Backend.SQL_GREATEROREQUAL_PAR);
generate(Object) dispatches on the node type:
| Node | Emits |
|---|---|
WurbletArgumentOperator |
sql.append(Backend.SQL_AND); — the operator's own Backend constant, so the keyword stays dialect-controlled. |
WurbletArgumentExpression |
A parenthesized recursion: SQL_LEFT_PARENTHESIS, the nested code, SQL_RIGHT_PARENTHESIS. |
WurbletArgument |
The condition itself — see below. |
| anything else | sql.append(<t>); |
Path arguments and the EXISTS clause¶
When an argument reaches its attribute through a relation path, the condition has to be expressed as a subquery.
The generator emits the EXISTS clause once per group — for the first argument of the group, the one the
parser gave a non-null getExistsRelations() (see
the parser's grouping rules).
It assembles:
EXISTS (followed by the table names and aliases of every component in the group (viaComponentInfo) and of every relation's foreign entity, comma-separated;WHERE, then the aggregate-root equations for the components —id = <component>.rootId, plus an extraid = idjoin when multi-table inheritance puts the attribute in a different table than the root id;- one join predicate per relation — for an object relation the local foreign-key column against the foreign
CN_ID, for a list relation the localCN_IDagainst the foreign key column — followed by the additional predicates of relations that carry more than one method argument.
The chain deliberately ends on a trailing .append(Backend.SQL_AND), because the argument's own column condition
is appended right after it. The closing parenthesis is emitted separately, when the parser flagged the argument as
isEndOfExistsClause().
Column conditions¶
For every column of the attribute's datatype (all of them for a plain =, or just the selected one), the
generator emits the column name followed by createRelopCode(argument). How the column name is qualified depends
on the context:
| Situation | Emitted |
|---|---|
| Path argument | <Impl>.CLASSVARIABLES.getColumnName(<Impl>.CN_…), or the literal embedded column name |
pathAllowed + multi-table inheritance |
<Impl>.CLASSVARIABLES.getColumnName(CN_…) |
pathAllowed + PDO |
getColumnName(CN_…), or the literal column name for an embedded attribute |
pathAllowed + low-level object |
the bare CN_… constant |
no pathAllowed |
the bare constant, prefixed with getColumnPrefix() + when embedded |
Optional conditions¶
wrapOptional encloses generated code in if (<condition>) { … } and re-indents it. It is applied three times
per argument: around the EXISTS preamble and around the closing parenthesis with the group's condition (the
|| of every boolean in the clause, from createOptionalCondition), and around the column condition with the
argument's own boolean. The result is a clause that appears in the SQL only when at least one of its conditions
is active — with sub-expressions that end up empty removed by Backend.reduceSql(…).
JoinClauseGenerator¶
JoinClauseGenerator is the second
CodeGenerator<Object>, used for the filter of a load join (*invoice|date:>=*lines). It is constructed with
the wurblet and one Join, and reached only from DbModelWurblet.createOptionalWhereForJoin.
The contrast with WhereClauseGenerator is the point:
WhereClauseGenerator |
JoinClauseGenerator |
|
|---|---|---|
| Output | Java code appending to sql at runtime |
literal SQL text |
| Operators | Backend.SQL_AND constants |
the plain words AND / OR |
| Column reference | getColumnName(CN_…) resolved at runtime |
<joinAlias>.<column> — the alias assigned by JoinPathFactory |
| Relop | createRelopCode |
createRelopSql |
The no-argument generate() prefixes the whole filter with Backend.SQL_AND, since it is appended to an existing
join condition. Because the text is fixed, a join filter cannot contain optional conditions — DbModelWurblet.run()
rejects them explicitly.
DslDeclarationFactory¶
DslDeclarationFactory is not a wurblet at
all — it is a plain factory driven by QueryDSL.wrbl,
producing the constants a query-DSL query is built
from. Each result is a record Declaration(String comment, String code) — the Javadoc text and the Java line,
without indentation.
The constructor rejects an entity that has no table to select from: an embedded one, or an abstract one whose
hierarchy maps to no table. It then seeds the defaults, all of which the QueryDSL options override:
| Property | Default | Option |
|---|---|---|
visibility |
private |
--public, --protected |
constantPrefix |
empty; getDefaultConstantPrefix() offers ENTITYNAME_ |
--prefix[=<prefix>] |
tableConstant |
the uppercased entity name | --table=<constant> |
tableAlias |
the model's table alias | --alias=<alias>, --noalias |
determineAttributes(javaNames) resolves the named attributes — or every mapped attribute when the list is empty
— and assertSameTable rejects an attribute stored in a different table, pointing at the entity that needs its
own block.
createFieldDeclaration derives the field's Java type from the attribute's effective DataType: the boxed
type for primitives (query values are objects), the external type for convertibles. Two cases are special:
- a generic Java type such as
Binary<T>has no class literal, so the field becomes an untypedField<Object>and the comment says why; - a datatype with a variant (the large string, for instance) cannot be identified by its class alone, so the
field is built from
DataTypeFactory.getInstance().get(String.class, "large")instead ofString.class.
RemoteIncludes¶
RemoteIncludes manages the two generated
TRIP files that make a PDO or operation remotable: the remote
delegate interface and its implementation. It is constructed by every remote-capable wurblet and does two
things.
Bootstrapping the files. All names come from NamingRules: the PDO/operation interface (from
getPdoClassName(), falling back to the class name), the remote interface and implementation, and their packages.
The target directories are located by stripping the package path off the wurblet's own source directory, so the
delegates always land in the same Maven module as the persistence implementation. If a file does not exist yet, it
is generated from a FreeMarker template — RemoteInterface.ftl or RemoteImplementation.ftl — taken from the
wurblet property templateDir, or from <projectRoot>/templates/{pdo|operation}. With neither property set the
whole run terminates.
The template model resolves the remote super types, special-casing the framework bases and deriving everything
else through NamingRules:
| Super class | Remote super interface / implementation | Package |
|---|---|---|
AbstractPersistentObject |
AbstractPersistentObjectRemoteDelegate / …Impl |
org.tentackle.persist.trip |
AbstractPersistentOperation |
AbstractPersistentOperationRemoteDelegate / …Impl |
org.tentackle.persist.trip |
AbstractDbObject |
AbstractDbObjectRemoteDelegate / …Impl |
org.tentackle.dbms.trip |
| anything else | derived from the super class's own name and package | derived |
For operations the model also records whether the source is an abstract operation, detected by matching
extends <Interface>< against the source text.
Carrying the generated methods. getInterfaceStream() and getImplementationStream() hand out the
PrintStreams of two wurbelizer heap files named <RemoteInterface>/methods and <RemoteImplementation>/methods.
Each query wurblet writes its remote counterpart there, and the remote sources pick it up through their own
Include anchors. discard() marks both heap files as discarded, so a wurblet reading incomplete content throws
a WurbelDiscardException instead of generating from it.
RemoteMethodHelper¶
RemoteMethodHelper backs the RemoteMethod
wurblet. Unlike everything else in this package it reads no model: it wraps a RemoteMethodInfo produced by the
tentackle-build-support annotation processor during the analyze run, i.e. the real compiled signature of the
method annotated @RemoteMethod.
The constructor records what the method returns (void, a PDO/DbObject, a collection of them, or a cursor — a
PDO implies the object flavors), whether it is static, and — when the wurblet was given --this — appends a
synthetic this parameter typed as the enclosing class.
The three parameter-string builders differ in exactly which parameters they drop and how this is rendered:
| Method | Skips | this becomes |
|---|---|---|
getDeclarationParameterString() |
session parameters | <PdoInterface> obj (varargs keep their ...) |
getInvocationParameterString() |
session parameters | me() for a PDO, the plain name otherwise |
getRemoteInvocationParameterString() |
the this parameter |
— (session parameters are kept) |
The session parameter is dropped from declarations and local invocations because the remote delegate already
carries the session; it survives in the remote invocation string. getUpdateDbInParametersStatements() returns a
getSession().applyTo(x) statement for every DbObject or PDO parameter, so objects arriving from a client are
re-bound to the server's session. getFirstName(), isFirstInstanceOfDb(), isFirstInstanceOfDomainContext()
and getParamCount() let the template decide how to shape the call, and getGenericReturnType() normalizes the
diamond <> to the empty string.
How a Select Wurblet Flows Through the Package¶
@wurblet selectRecent PdoSelectList --remote processed:>= or processed:=:null +id *address
│
▼
DbModelWurblet.run()
│ ModelWurblet loads the model and resolves the Entity
│ WurbletArgumentParser splits: expression | sorting | joins
│ getJoinPaths() consolidates and normalizes the joins, naming them j_1, j_1_1, …
▼
the template (PdoSelectList.wrbl) calls, in order:
│
├─ buildMethodParameters() ........ Timestamp processed (the method signature)
├─ createStatementId() ............ SELECT_RECENT_STMT
├─ genwhere.incl
│ └─ expression.toCode(new WhereClauseGenerator(this))
│ └─ per argument: createRelopCode() → sql.append(Backend.SQL_…)
├─ createOrderBy() ................ .append(getColumnName(CN_ID)).append(SQL_SORTASC)
├─ createJoins() .................. JoinedSelect<T> js = …addJoin(…)
│ └─ per filtered join: new JoinClauseGenerator(this, join).generate()
│ └─ createRelopSql() → literal " AND j_1.date>=?"
├─ gensetpar.incl
│ ├─ createJoinSetPars() ...... bind the join value arguments (first!)
│ └─ createWhereSetPars() ..... bind the WHERE arguments
└─ RemoteIncludes.getInterfaceStream() / getImplementationStream()
the matching remote delegate method
The binding order is the reason getMethodArguments() puts join-filter arguments before the expression
arguments: createJoins() emits the join predicates into the SQL before the WHERE clause, so their ?
placeholders come first.
Error Handling¶
The conventions are those of the interface-side package. Specific to this package:
| Condition | Reaction |
|---|---|
| A relation path where the template does not allow one | WurbelException — "relation paths not allowed in wurblet …" |
| An optional condition inside a join filter, or in a statement-caching wurblet | WurbelException |
| A joined non-composite relation that is not serialized, under remoting | WurbelException — it could not be transported |
| An array operator no active backend supports | WurbelException via assertSupportedByBackends |
A backend-specific column count in a WHERE clause or a join |
WurbelException |
| A sort key from a joined entity with no matching join | WurbelException — "missing join for sort key" |
Neither templateDir nor projectRoot set when a remote file must be generated |
WurbelTerminationException — aborts the whole run |
| A remote heap file whose content is incomplete | RemoteIncludes.discard(), so readers raise WurbelDiscardException |
Testing¶
Unlike the interface-side package, most of this one is covered by unit tests:
DbModelWurbletTest,
WhereClauseGeneratorTest,
JoinClauseGeneratorTest,
DslDeclarationFactoryTest and
RemoteMethodHelperTest.
They share WurbletTestSupport, which
builds a real Entity from a model-definition string — covering a single-column type, a multi-column one
(BMoney) and a nullable one — rather than mocking the model API, and instantiates the wurblet directly. That
works because everything tested is a pure function of the model plus the wurblet arguments: no Wurbler
container and no source file to weave into. It is the shortest path to understanding what any of these generators
actually emits.
Related Documentation¶
- Persistence Wurblets — the templates and what they generate.
- The Wurblet API — the base classes, the argument mini-language and the join model this package consumes.
- PdoSelectList and PdoSelectUnique — the query language from the user's point of view.
- Eager Relations — what
createJoins()produces, and why eager loading never cascades. - Wurblets — Interface-Level Code Generation — the interface-side counterpart.
- The Query DSL — what
DslDeclarationFactoryfeeds. - TRIP — the remoting protocol behind the generated delegates.
- Tentackle SQL — the
DataType/Backendconstants every generated statement is built from. - Model Definition Syntax — the model driving it all.