Compare canonical paths by token spelling
This commit is contained in:
@@ -135,7 +135,7 @@ Roadmap candidate 9. Owner: `internal/engine/inheritance_load.go`.
|
|||||||
Validate: `go test ./internal/engine -run 'Inheritance|Inherited'` and the
|
Validate: `go test ./internal/engine -run 'Inheritance|Inherited'` and the
|
||||||
everyday check. Inputs without inheritance still need a linear scan.
|
everyday check. Inputs without inheritance still need a linear scan.
|
||||||
|
|
||||||
## Stage 4: Compare path tokens without whole-pointer formatting
|
## Stage 4: Compare path tokens without whole-pointer formatting — Complete
|
||||||
|
|
||||||
Roadmap candidate 2. Owners: engine `path.go`, `resolution.go`, `metadata.go`.
|
Roadmap candidate 2. Owners: engine `path.go`, `resolution.go`, `metadata.go`.
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (m Metadata) effectiveField(path Path) (FieldInfo, bool) {
|
|||||||
}
|
}
|
||||||
pointer := path.String()
|
pointer := path.String()
|
||||||
candidate, ok := m.effectivePaths[pointer]
|
candidate, ok := m.effectivePaths[pointer]
|
||||||
if !ok || !matchesPathShapeWithPointer(path, candidate, pointer) {
|
if !ok || !matchesPathShape(path, candidate) {
|
||||||
return FieldInfo{}, false
|
return FieldInfo{}, false
|
||||||
}
|
}
|
||||||
info, ok := m.fields[pointer]
|
info, ok := m.fields[pointer]
|
||||||
@@ -89,18 +89,14 @@ func (m Metadata) HistoricalPaths() []Path {
|
|||||||
// Textual pointers are lookup keys, but explicit key/index kinds remain part of
|
// Textual pointers are lookup keys, but explicit key/index kinds remain part of
|
||||||
// the query. Candidate paths are constructed from actual document containers.
|
// the query. Candidate paths are constructed from actual document containers.
|
||||||
func matchesPathShape(query, candidate Path) bool {
|
func matchesPathShape(query, candidate Path) bool {
|
||||||
if !query.valid() {
|
if !query.valid() || len(query.segments) != len(candidate.segments) {
|
||||||
return false
|
|
||||||
}
|
|
||||||
return matchesPathShapeWithPointer(query, candidate, query.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchesPathShapeWithPointer(query, candidate Path, pointer string) bool {
|
|
||||||
if pointer != candidate.String() || len(query.segments) != len(candidate.segments) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for i, segment := range query.segments {
|
for i, segment := range query.segments {
|
||||||
actual := candidate.segments[i]
|
actual := candidate.segments[i]
|
||||||
|
if !pathTokenSpellingEqual(segment, actual) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if actual.kind == pathSegmentIndex {
|
if actual.kind == pathSegmentIndex {
|
||||||
index, ok := schemaIndex(segment)
|
index, ok := schemaIndex(segment)
|
||||||
if !ok || index != actual.index {
|
if !ok || index != actual.index {
|
||||||
|
|||||||
@@ -128,6 +128,45 @@ func clonePathSegments(segments []pathSegment, extra int) []pathSegment {
|
|||||||
return cloned
|
return cloned
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pathTokenSpellingEqual(left, right pathSegment) bool {
|
||||||
|
switch {
|
||||||
|
case left.kind == pathSegmentIndex && right.kind == pathSegmentIndex:
|
||||||
|
return left.index == right.index
|
||||||
|
case left.kind == pathSegmentIndex:
|
||||||
|
return pathIndexSpellingEqual(left.index, right.key)
|
||||||
|
case right.kind == pathSegmentIndex:
|
||||||
|
return pathIndexSpellingEqual(right.index, left.key)
|
||||||
|
default:
|
||||||
|
return left.key == right.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathIndexSpellingEqual(index int, token string) bool {
|
||||||
|
var scratch [20]byte
|
||||||
|
spelling := strconv.AppendInt(scratch[:0], int64(index), 10)
|
||||||
|
if len(spelling) != len(token) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, character := range spelling {
|
||||||
|
if character != token[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathSpellingEqual(left, right Path) bool {
|
||||||
|
if len(left.segments) != len(right.segments) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, segment := range left.segments {
|
||||||
|
if !pathTokenSpellingEqual(segment, right.segments[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func escapePathToken(token string) string {
|
func escapePathToken(token string) string {
|
||||||
token = strings.ReplaceAll(token, "~", "~0")
|
token = strings.ReplaceAll(token, "~", "~0")
|
||||||
return strings.ReplaceAll(token, "/", "~1")
|
return strings.ReplaceAll(token, "/", "~1")
|
||||||
|
|||||||
@@ -48,6 +48,106 @@ func TestPathRoundTripsSemanticKeys(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPathContainmentMatchesPointerSpelling(t *testing.T) {
|
||||||
|
parse := func(pointer string) Path {
|
||||||
|
path, err := ParsePath(pointer)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParsePath(%q) error = %v", pointer, err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
maximum := int(^uint(0) >> 1)
|
||||||
|
minimum := -maximum - 1
|
||||||
|
paths := []Path{
|
||||||
|
{},
|
||||||
|
Keys(""),
|
||||||
|
Keys("a"),
|
||||||
|
Keys("ab"),
|
||||||
|
Keys("a", "b"),
|
||||||
|
Keys("a/b"),
|
||||||
|
Keys("a~1b"),
|
||||||
|
Keys("~"),
|
||||||
|
Keys("0"),
|
||||||
|
Keys("01"),
|
||||||
|
Keys("+1"),
|
||||||
|
Keys("items").Index(0),
|
||||||
|
Keys("items").Index(maximum),
|
||||||
|
Keys("items").Index(minimum),
|
||||||
|
parse("/"),
|
||||||
|
parse("/a~1b"),
|
||||||
|
parse("/a~01b"),
|
||||||
|
parse("/~0"),
|
||||||
|
parse("/0"),
|
||||||
|
parse("/01"),
|
||||||
|
parse("/+1"),
|
||||||
|
parse("/items/0"),
|
||||||
|
parse("/items/" + strconv.Itoa(maximum)),
|
||||||
|
parse("/items/" + strconv.Itoa(minimum)),
|
||||||
|
Path{}.Index(-1),
|
||||||
|
}
|
||||||
|
for _, container := range paths {
|
||||||
|
for _, descendant := range paths {
|
||||||
|
got := pathContains(container, descendant)
|
||||||
|
want := pathContainsByPointer(container, descendant)
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("pathContains(%q, %q) = %v, want %v", container.String(), descendant.String(), got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendUniquePathMatchesPointerSpelling(t *testing.T) {
|
||||||
|
parse := func(pointer string) Path {
|
||||||
|
path, err := ParsePath(pointer)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParsePath(%q) error = %v", pointer, err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
paths := []Path{
|
||||||
|
Keys("a/b"), parse("/a~1b"),
|
||||||
|
Keys("a~1b"), parse("/a~01b"),
|
||||||
|
Keys("items").Index(0), parse("/items/0"), Keys("items", "0"),
|
||||||
|
Keys("items", "01"), parse("/items/01"),
|
||||||
|
Path{}.Index(-1), Keys("-1"),
|
||||||
|
Keys("a", "b"), Keys("a/b"),
|
||||||
|
}
|
||||||
|
var got, want []Path
|
||||||
|
for _, path := range paths {
|
||||||
|
got = appendUniquePath(got, path)
|
||||||
|
want = appendUniquePathByPointer(want, path)
|
||||||
|
}
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Fatalf("unique path counts = %d, want %d", len(got), len(want))
|
||||||
|
}
|
||||||
|
for i := range got {
|
||||||
|
if got[i].String() != want[i].String() {
|
||||||
|
t.Fatalf("unique path %d = %q, want %q", i, got[i].String(), want[i].String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathContainsByPointer(container, descendant Path) bool {
|
||||||
|
if !container.valid() || !descendant.valid() || len(container.segments) > len(descendant.segments) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
prefix := container.String()
|
||||||
|
if prefix == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
value := descendant.String()
|
||||||
|
return value == prefix || len(value) > len(prefix) && value[:len(prefix)] == prefix && value[len(prefix)] == '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUniquePathByPointer(paths []Path, path Path) []Path {
|
||||||
|
for _, existing := range paths {
|
||||||
|
if existing.String() == path.String() {
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(paths, path.clone())
|
||||||
|
}
|
||||||
|
|
||||||
func TestPathPreservesSegmentIntentAndImmutability(t *testing.T) {
|
func TestPathPreservesSegmentIntentAndImmutability(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -337,12 +337,12 @@ func pathContains(container, descendant Path) bool {
|
|||||||
if !container.valid() || !descendant.valid() || len(container.segments) > len(descendant.segments) {
|
if !container.valid() || !descendant.valid() || len(container.segments) > len(descendant.segments) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if container.String() == "" {
|
for i, segment := range container.segments {
|
||||||
return true
|
if !pathTokenSpellingEqual(segment, descendant.segments[i]) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
prefix := container.String()
|
}
|
||||||
value := descendant.String()
|
return true
|
||||||
return value == prefix || strings.HasPrefix(value, prefix+"/")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendUniqueString(values []string, value string) []string {
|
func appendUniqueString(values []string, value string) []string {
|
||||||
@@ -354,7 +354,7 @@ func appendUniqueString(values []string, value string) []string {
|
|||||||
|
|
||||||
func appendUniquePath(paths []Path, path Path) []Path {
|
func appendUniquePath(paths []Path, path Path) []Path {
|
||||||
for _, existing := range paths {
|
for _, existing := range paths {
|
||||||
if existing.String() == path.String() {
|
if pathSpellingEqual(existing, path) {
|
||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user