Skip to content

where("string value field").startsWith("x") always tries to construct string value("x") #225

@nvamelichev

Description

@nvamelichev

E.g.:

db.table(Membership.class).query()
    .where("memberId").startsWith("xyzzy-")
    .index("memberIdIndex", IndexOrder.ASCENDING)
    .findOne();

will throw if StringValue("xyzzy-") constructor throws... But only in the YDB implementation repository-ydb-v2, not the in-memory implementation in repository-inmemory!

This is a consequence of YqlListingQuery always using FieldValue.getRaw() when transforming the FilterExpression tree (implementation-independent) into YqlPredicate expression tree (YDB implementation detail: YQL code generation).

An easy fix is to explicitly get a String value from each string-related FilterExpressions, and pass that same String to YqlPredicates that also deal exclusively with Strings.

Analysis by agent

• Yes. In the ORM layer, the bad cast happens in YOJ when it builds the YQL predicate, not in
startsWith() itself.

  • startsWith() in /tmp/yoj-project/databind/src/main/java/tech/ydb/yoj/databind/expression/
    FilterBuilder.java:281 is generic. It just wraps the prefix via FieldValue.ofObj(value,
    field.getJavaField()) at /tmp/yoj-project/databind/src/main/java/tech/ydb/yoj/databind/
    expression/FilterBuilder.java:188 and stores it in a ScalarExpr at /tmp/yoj-project/databind/
    src/main/java/tech/ydb/yoj/databind/expression/FilterBuilder.java:197.
  • The actual conversion back to the field’s Java type happens in /tmp/yoj-project/repository-
    ydb-v2/src/main/java/tech/ydb/yoj/repository/ydb/yql/YqlListingQuery.java:59, which does:
    scalarExpr.getValue().getRaw(scalarExpr.getField())
  • getRaw() in /tmp/yoj-project/databind/src/main/java/tech/ydb/yoj/databind/expression/values/
    FieldValue.java:45 calls CustomValueTypes.postconvert(field, cmp) at /tmp/yoj-project/
    databind/src/main/java/tech/ydb/yoj/databind/expression/values/FieldValue.java:49. That is the
    point where YOJ converts the DB/text representation back into the Java field type.
  • For your field, the Java type is still StringValue: tezd/Membership.kt:59 defines MemberId(val value: StringValue),
    and the queried field is tezd/Membership.kt:84 memberId.value.
  • StringValue is a YOJ @StringValueType in tezd/StringValue.java:44, so YOJ uses /tmp/yoj-project/databind/src/main/java/tech/ydb/yoj/databind/
    converter/StringValueConverter.java:44. That converter looks at field.getRawType() and
    deserializes to that Java type; for StringValue it resolves fromString(String) at tezd/StringValue.java:82, which calls parse(...).

So the precise cast path is:

YqlListingQuery.visitScalarExpr
-> FieldValue.getRaw(field)
-> CustomValueTypes.postconvert(...)
-> StringValueConverter.toJava(...)
-> StringValue.fromString(...)
-> StringValue.parse("xyzzy-")

That is why startsWith on a text-backed StringValue field still tries to parse the prefix as a full StringValue,
which is the wrong behavior for a text prefix operator.

By inspection, this is a YOJ bug in the LIKE/STARTS_WITH path: it asks for the Java-side value
via getRaw() when it should stay on the column/text-side representation.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions