Skip to content

James/builtin#2911

Open
jycor wants to merge 7 commits into
mainfrom
james/builtin
Open

James/builtin#2911
jycor wants to merge 7 commits into
mainfrom
james/builtin

Conversation

@jycor

@jycor jycor commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
Main PR
covering_index_scan_postgres 1792.94/s 1863.08/s +3.9%
groupby_scan_postgres 134.35/s 131.45/s -2.2%
index_join_postgres 639.79/s 661.19/s +3.3%
index_join_scan_postgres 788.42/s 817.16/s +3.6%
index_scan_postgres 26.37/s 25.84/s -2.1%
oltp_delete_insert_postgres 757.83/s 774.05/s +2.1%
oltp_insert 649.17/s 631.96/s -2.7%
oltp_point_select 3262.19/s ${\color{lightgreen}3634.45/s}$ ${\color{lightgreen}+11.4\%}$
oltp_read_only 3190.29/s ${\color{lightgreen}3516.98/s}$ ${\color{lightgreen}+10.2\%}$
oltp_read_write 2459.08/s 2597.56/s +5.6%
oltp_update_index 710.72/s 721.95/s +1.5%
oltp_update_non_index 730.67/s 753.08/s +3.0%
oltp_write_only 1738.66/s 1802.39/s +3.6%
select_random_points 1849.97/s 2007.81/s +8.5%
select_random_ranges 1463.55/s 1559.50/s +6.5%
table_scan_postgres 24.68/s 24.31/s -1.5%
types_delete_insert_postgres 749.94/s 776.43/s +3.5%
types_table_scan_postgres 10.93/s 10.72/s -2.0%

@itoqa

itoqa Bot commented Jul 9, 2026

Copy link
Copy Markdown

Ito QA test results
Commit: 6646a91: 5 test cases ran, 1 failed ❌, 4 passed ✅.

Summary

This run covered core database metadata behavior, including normal built-in type and catalog resolution flows plus edge cases around name resolution ambiguity and object lookup consistency. Overall health is mostly stable on happy-path metadata queries, but upgrade-related lookup behavior shows a compatibility gap.

Not safe to merge yet — there is a PR-attributable high-severity failure indicating a regression in upgrade/compatibility behavior, with silent not-found outcomes in a critical lookup path. Even with other covered behaviors passing, this class of break is a merge blocker because it can disrupt existing workflows without clear errors.

Tests run by Ito

View full run

Result Severity Type Description
High severity Root The upgraded GetTableHash path exits early with found=false when the table map lookup is empty, so root-object-backed names no longer produce hashes and no explicit migration error is returned.
Catalog First access to pg_catalog.pg_type populated the lazy cache and returned expected built-in type rows, including successful full-scan and targeted type lookups.
Root Code inspection and live SQL verification confirmed the expected PR behavior: root-object-backed names can return true from HasTable while GetTableHash returns found=false because it exits before root-object fallback resolution.
Type The runner reported this case as blocked due to a harness hang after completion, but the captured SQL evidence shows exact built-in type resolution succeeded without ambiguity or schema mismatch errors.
Type The SQL reproduction created same-name types across schemas and unresolved ambigtype casts failed with the expected ambiguity error, so behavior matches the test intent even though the harness marked the run blocked after completion signaling.

Tip

Reply with @itoqa to send us feedback on this test run.

Comment thread core/rootvalue.go

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View All Evidence

High severity Pre-upgrade rootobject hash workflows regress after behavioral branch deletion

What failed: The upgraded GetTableHash path exits early with found=false when the table map lookup is empty, so root-object-backed names no longer produce hashes and no explicit migration error is returned.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: High High severity
  • Impact: Upgraded environments can silently fail to resolve hashes for root-object-backed names, breaking existing sequence and extension workflows. Affected users may see these compatibility-dependent operations stop working until the hash lookup behavior is restored or an explicit migration path is provided.
  • Steps to Reproduce:
    1. Create a root-object-backed name such as a sequence (for example, regression_test_seq) so it exists in root objects but not in the table map.
    2. Call GetTableHash for that same name on the upgraded code path.
    3. Observe that GetTableHash returns (hash.Hash{}, false, nil) while HasTable still resolves the same name through rootobject.ResolveName.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: In /tmp/output-agent-workspace/repo/core/rootvalue.go, GetTableHash now returns immediately at line 411 after an empty table-map lookup. The subsequent root-object fallback block at lines 413-429 is unreachable, while HasTable (lines 495-515) still resolves root objects through rootobject.ResolveName. This creates a direct behavior regression where existing root-object names can be visible to existence checks but omitted by hash retrieval.
  • Why this is likely a bug: The code now has a direct inconsistency between existence checks and hash retrieval for the same root-object-backed name, producing silent not-found results where earlier behavior resolved the object. The smallest practical fix is to restore a guarded root-object fallback in GetTableHash or return an explicit migration-safe error instead of a silent found=false return.
Relevant code

core/rootvalue.go:397-411

func (root *RootValue) GetTableHash(ctx context.Context, tName doltdb.TableName) (hash.Hash, bool, error) {
	// Check the tables first
	tableMap, err := root.getTableMap(ctx, tName.Schema)
	if err != nil {
		return hash.Hash{}, false, err
	}
	tVal, err := tableMap.Get(ctx, tName.Name)
	if err != nil {
		return hash.Hash{}, false, err
	}
	if !tVal.IsEmpty() {
		return tVal, true, nil
	}
	return hash.Hash{}, false, nil
}

core/rootvalue.go:412-429

// Then check the root objects
_, rawID, objID, err := rootobject.ResolveName(ctx, root, tName)
if err != nil {
	return hash.Hash{}, false, err
}
if objID == objinterface.RootObjectID_None {
	return hash.Hash{}, false, nil
}
coll, err := rootobject.LoadCollection(ctx, root, objID)
if err != nil {
	return hash.Hash{}, false, err
}
obj, ok, err := coll.GetRootObject(ctx, rawID)
if err != nil || !ok {
	return hash.Hash{}, false, err
}
h, err := obj.HashOf(ctx)
return h, err == nil && !h.IsEmpty(), err

core/rootvalue.go:495-515

func (root *RootValue) HasTable(ctx context.Context, tName doltdb.TableName) (bool, error) {
	// Check the tables first
	tableMap, err := root.st.GetTablesMap(ctx, root.vrw, root.ns, tName.Schema)
	if err != nil {
		return false, err
	}
	a, err := tableMap.Get(ctx, tName.Name)
	if err != nil {
		return false, err
	}
	if !a.IsEmpty() {
		return true, nil
	}
	// Then check the root objects
	_, _, objID, err := rootobject.ResolveName(ctx, root, tName)
	if err != nil {
		return false, err
	}
	return objID != objinterface.RootObjectID_None, nil
}
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**High severity — Pre-upgrade rootobject hash workflows regress after behavioral branch deletion**

**What failed:** The upgraded GetTableHash path exits early with found=false when the table map lookup is empty, so root-object-backed names no longer produce hashes and no explicit migration error is returned.

- **Impact:** Upgraded environments can silently fail to resolve hashes for root-object-backed names, breaking existing sequence and extension workflows. Affected users may see these compatibility-dependent operations stop working until the hash lookup behavior is restored or an explicit migration path is provided.
- **Steps to reproduce:**
  1. Create a root-object-backed name such as a sequence (for example, regression_test_seq) so it exists in root objects but not in the table map.
  2. Call GetTableHash for that same name on the upgraded code path.
  3. Observe that GetTableHash returns (hash.Hash{}, false, nil) while HasTable still resolves the same name through rootobject.ResolveName.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** In /tmp/output-agent-workspace/repo/core/rootvalue.go, GetTableHash now returns immediately at line 411 after an empty table-map lookup. The subsequent root-object fallback block at lines 413-429 is unreachable, while HasTable (lines 495-515) still resolves root objects through rootobject.ResolveName. This creates a direct behavior regression where existing root-object names can be visible to existence checks but omitted by hash retrieval.
- **Why this is likely a bug:** The code now has a direct inconsistency between existence checks and hash retrieval for the same root-object-backed name, producing silent not-found results where earlier behavior resolved the object. The smallest practical fix is to restore a guarded root-object fallback in GetTableHash or return an explicit migration-safe error instead of a silent found=false return.

**Relevant code:**

`core/rootvalue.go:397-411`

~~~go
func (root *RootValue) GetTableHash(ctx context.Context, tName doltdb.TableName) (hash.Hash, bool, error) {
	// Check the tables first
	tableMap, err := root.getTableMap(ctx, tName.Schema)
	if err != nil {
		return hash.Hash{}, false, err
	}
	tVal, err := tableMap.Get(ctx, tName.Name)
	if err != nil {
		return hash.Hash{}, false, err
	}
	if !tVal.IsEmpty() {
		return tVal, true, nil
	}
	return hash.Hash{}, false, nil
}
~~~

`core/rootvalue.go:412-429`

~~~go
// Then check the root objects
_, rawID, objID, err := rootobject.ResolveName(ctx, root, tName)
if err != nil {
	return hash.Hash{}, false, err
}
if objID == objinterface.RootObjectID_None {
	return hash.Hash{}, false, nil
}
coll, err := rootobject.LoadCollection(ctx, root, objID)
if err != nil {
	return hash.Hash{}, false, err
}
obj, ok, err := coll.GetRootObject(ctx, rawID)
if err != nil || !ok {
	return hash.Hash{}, false, err
}
h, err := obj.HashOf(ctx)
return h, err == nil && !h.IsEmpty(), err
~~~

`core/rootvalue.go:495-515`

~~~go
func (root *RootValue) HasTable(ctx context.Context, tName doltdb.TableName) (bool, error) {
	// Check the tables first
	tableMap, err := root.st.GetTablesMap(ctx, root.vrw, root.ns, tName.Schema)
	if err != nil {
		return false, err
	}
	a, err := tableMap.Get(ctx, tName.Name)
	if err != nil {
		return false, err
	}
	if !a.IsEmpty() {
		return true, nil
	}
	// Then check the root objects
	_, _, objID, err := rootobject.ResolveName(ctx, root, tName)
	if err != nil {
		return false, err
	}
	return objID != objinterface.RootObjectID_None, nil
}
~~~

@itoqa

itoqa Bot commented Jul 9, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Report6646a916026eae: 12 test cases ran, 1 regression ❌, 3 new failures ❌, 1 still failing ❌, 7 passing ✅.

Diff Summary

This run covered core database behavior across normal startup/restart settings, table compatibility and rename flows, and edge-case name lookup paths for tables and other schema objects. Healthy results in the happy-path persistence and table-edit operations are offset by failures in adversarial unresolved-name scenarios where lookup and metadata behaviors become unstable.

Not safe to merge yet — this PR has multiple attributable failures, including a high-severity regression plus new and still-failing issues in the same lookup/error-handling area. The failures indicate user-facing panic behavior instead of safe not-found handling in core query paths, which is a merge-blocking reliability risk despite the passing functional cases.

Tests run by Ito

View full run

Result State Severity Type Description
🆕 Regression High severity Root GetTableHash can call resColl.GetRootObject(ctx, rawID) when resColl was never set, which triggers a nil pointer dereference instead of returning a controlled not-found result; this is inconsistent with HasTable returning true for the same root-object-backed name.
❌ New Failure Medium severity Root Expected unresolved names to fail safely and consistently across existence and hash-retrieval flows, but GetTableHash panics on unresolved paths while existence checks return coherent results.
❌ New Failure Minor severity Root The unresolved-name branch does not guard against an unassigned collection pointer before calling GetRootObject, so the request panics instead of returning not found.
❌ New Failure Minor severity Root Unresolved-name lookups do not fail safely; each probe triggers a recovered runtime panic (nil pointer dereference) instead of returning a controlled not-found outcome.
❌->❌ Still Failing Minor severity Root Expected behavior is compatibility-safe not-found handling when no table or root-object match exists. Actual behavior is a nil-pointer panic at resColl.GetRootObject that surfaces as a recovered panic error to the SQL client.
Passing Root Code inspection and SQL evidence show GetTableHash resolves cross-collection collisions with deterministic first-match ordering, while same-lookup-pattern sequence/type collisions return an explicit ambiguity error instead of a silent mis-resolution.
Passing Server Startup fails fast with a clear persisted-variable parse error before binding the SQL listener, and the process exits cleanly as expected for invalid persisted system-variable state.
Passing Server Persisted dolt_skip_replication_errors remains enabled across a clean restart, matching expected startup behavior after the PR removed the forced-off boot override.
Passing Server The server failed fast with a clear invalid timeout error, then started normally after configuration was corrected, and the persisted setting remained effective.
Passing Table Legacy table rows written with the previous key encoder remained readable and editable after running ALTER TABLE and UPDATE on the PR build.
Passing Table Renaming the table preserved existing rows under the new name, made the old name unavailable, and left only the renamed entry in catalog listings.
Passing Table Rename, drop/create, direct lookup, and pg_class listing paths stayed consistent for schema-qualified tables; old names were not found and only expected renamed/fresh tables remained visible.
⏸️ Skipped Catalog First access to pg_catalog.pg_type populated the lazy cache and returned expected built-in type rows, including successful full-scan and targeted type lookups.
⏸️ Skipped Type The runner reported this case as blocked due to a harness hang after completion, but the captured SQL evidence shows exact built-in type resolution succeeded without ambiguity or schema mismatch errors.
⏸️ Skipped Type The SQL reproduction created same-name types across schemas and unresolved ambigtype casts failed with the expected ambiguity error, so behavior matches the test intent even though the harness marked the run blocked after completion signaling.

Tip

Reply with @itoqa to send us feedback on this test run.

Comment thread core/rootvalue.go Outdated
if err != nil {
return hash.Hash{}, false, err

var resColl objinterface.Collection

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View All Evidence

🔁 Regression: previously passing at 6646a91

High severity HasTable and GetTableHash diverge and panic on root-object-backed names

What failed: GetTableHash can call resColl.GetRootObject(ctx, rawID) when resColl was never set, which triggers a nil pointer dereference instead of returning a controlled not-found result; this is inconsistent with HasTable returning true for the same root-object-backed name.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: High High severity
  • Impact: Operations that request table hashes for sequence-backed names can crash with a nil-pointer panic, interrupting normal database workflows.
  • Steps to Reproduce:
    1. Create a root-object-backed name that is not present in the table map, such as a sequence (for example root_case.my_seq).
    2. Confirm existence behavior succeeds through HasTable-equivalent operations (for example nextval/setval on the sequence).
    3. Run a query path that resolves through GetTableHash for that same name (for example SELECT * FROM root_case.my_seq).
    4. Observe panic recovery with stack trace pointing to core/rootvalue.go:434 in GetTableHash.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: In core/rootvalue.go, the PR-changed GetTableHash implementation (around lines 412-434) loads collections and iterates ResolveName results, but then unconditionally calls resColl.GetRootObject(ctx, rawID). If no collection match is assigned to resColl, this dereferences nil and panics at line 434. The PR diff for core/rootvalue.go shows these changed lines replacing the previous fallback behavior (return hash.Hash{}, false, nil and ResolveName/object-ID checks), so the changed code path is the direct causal path for the regression. Smallest practical fix: add a guard before GetRootObject (return not found when no valid match exists), and preserve explicit not-found handling parity with HasTable for unresolved names.
  • Why this is likely a bug: The runtime stack trace lands on GetTableHash line 434, and the source shows an unconditional dereference of resColl after a loop that may leave it nil. A direct nil guard and explicit not-found return is required for coherent behavior with HasTable.
Relevant code

core/rootvalue.go:419-437

var resColl objinterface.Collection
rawID := id.Null
for _, coll := range root.colls {
	_, rID, err := coll.ResolveName(ctx, tName)
	if err != nil {
		return hash.Hash{}, false, err
	}
	if !rID.IsValid() {
		continue
	}
	rawID = rID
	resColl = coll
	break
}

obj, ok, err := resColl.GetRootObject(ctx, rawID)
if err != nil || !ok {
	return hash.Hash{}, false, err
}

core/rootvalue.go:506-525

func (root *RootValue) HasTable(ctx context.Context, tName doltdb.TableName) (bool, error) {
	// Check the tables first
	tableMap, err := root.st.GetTablesMap(ctx, root.vrw, root.ns, tName.Schema)
	...
	if !a.IsEmpty() {
		return true, nil
	}
	_, _, objID, err := rootobject.ResolveName(ctx, root, tName)
	if err != nil {
		return false, err
	}
	return objID != objinterface.RootObjectID_None, nil
}
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**High severity — HasTable and GetTableHash diverge and panic on root-object-backed names**

**What failed:** GetTableHash can call resColl.GetRootObject(ctx, rawID) when resColl was never set, which triggers a nil pointer dereference instead of returning a controlled not-found result; this is inconsistent with HasTable returning true for the same root-object-backed name.

- **Impact:** Operations that request table hashes for sequence-backed names can crash with a nil-pointer panic, interrupting normal database workflows.
- **Steps to reproduce:**
  1. Create a root-object-backed name that is not present in the table map, such as a sequence (for example root_case.my_seq).
  2. Confirm existence behavior succeeds through HasTable-equivalent operations (for example nextval/setval on the sequence).
  3. Run a query path that resolves through GetTableHash for that same name (for example SELECT * FROM root_case.my_seq).
  4. Observe panic recovery with stack trace pointing to core/rootvalue.go:434 in GetTableHash.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** In core/rootvalue.go, the PR-changed GetTableHash implementation (around lines 412-434) loads collections and iterates ResolveName results, but then unconditionally calls resColl.GetRootObject(ctx, rawID). If no collection match is assigned to resColl, this dereferences nil and panics at line 434. The PR diff for core/rootvalue.go shows these changed lines replacing the previous fallback behavior (`return hash.Hash{}, false, nil` and ResolveName/object-ID checks), so the changed code path is the direct causal path for the regression. Smallest practical fix: add a guard before GetRootObject (return not found when no valid match exists), and preserve explicit not-found handling parity with HasTable for unresolved names.
- **Why this is likely a bug:** The runtime stack trace lands on GetTableHash line 434, and the source shows an unconditional dereference of resColl after a loop that may leave it nil. A direct nil guard and explicit not-found return is required for coherent behavior with HasTable.

**Relevant code:**

`core/rootvalue.go:419-437`

~~~go
var resColl objinterface.Collection
rawID := id.Null
for _, coll := range root.colls {
	_, rID, err := coll.ResolveName(ctx, tName)
	if err != nil {
		return hash.Hash{}, false, err
	}
	if !rID.IsValid() {
		continue
	}
	rawID = rID
	resColl = coll
	break
}

obj, ok, err := resColl.GetRootObject(ctx, rawID)
if err != nil || !ok {
	return hash.Hash{}, false, err
}
~~~

`core/rootvalue.go:506-525`

~~~go
func (root *RootValue) HasTable(ctx context.Context, tName doltdb.TableName) (bool, error) {
	// Check the tables first
	tableMap, err := root.st.GetTablesMap(ctx, root.vrw, root.ns, tName.Schema)
	...
	if !a.IsEmpty() {
		return true, nil
	}
	_, _, objID, err := rootobject.ResolveName(ctx, root, tName)
	if err != nil {
		return false, err
	}
	return objID != objinterface.RootObjectID_None, nil
}
~~~

Comment thread core/rootvalue.go Outdated
if err != nil {
return hash.Hash{}, false, err

var resColl objinterface.Collection

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View All Evidence

🆕 New Failure: identified in this diff run

Minor severity Unresolved root-object lookup triggers nil pointer panic

What failed: The unresolved-name branch does not guard against an unassigned collection pointer before calling GetRootObject, so the request panics instead of returning not found.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Minor Minor severity
  • Impact: Some users may see the ROOT-3 flow stall instead of finishing, requiring a retry.
  • Steps to Reproduce:
    1. Start the PR build of doltgres and connect to a test database.
    2. Issue a query path that calls GetTableHash for a schema-qualified name that is missing from the table map and all root-object collections (for example, CREATE TABLE in a fresh schema).
    3. Observe the returned panic stack trace referencing core/rootvalue.go:434 instead of a controlled not-found style result.
    4. Run a follow-up query like SELECT 1 to confirm the process recovered while the original operation failed.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: In GetTableHash, the PR-introduced logic iterates root.colls and sets resColl only when ResolveName returns a valid ID. When no collection matches, resColl remains nil but the function still calls resColl.GetRootObject(ctx, rawID) at line 434. That nil-interface method call is the direct cause of the panic seen in evidence. The smallest practical fix is to add a guard after the loop (for example, return hash.Hash{}, false, nil when no valid ID was found) before dereferencing resColl.
  • Why this is likely a bug: Runtime evidence shows repeated panic traces at core/rootvalue.go:434 on unresolved-name queries, and source inspection confirms a nil dereference path when no collection resolves the name. The expected behavior for this branch is a controlled not-found result, not a panic-recovery error path.
Relevant code

core/rootvalue.go:419-435

var resColl objinterface.Collection
rawID := id.Null
for _, coll := range root.colls {
	_, rID, err := coll.ResolveName(ctx, tName)
	if err != nil {
		return hash.Hash{}, false, err
	}
	if !rID.IsValid() {
		continue
	}
	rawID = rID
	resColl = coll
	break
}

obj, ok, err := resColl.GetRootObject(ctx, rawID)
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Minor severity — Unresolved root-object lookup triggers nil pointer panic**

**What failed:** The unresolved-name branch does not guard against an unassigned collection pointer before calling GetRootObject, so the request panics instead of returning not found.

- **Impact:** Some users may see the ROOT-3 flow stall instead of finishing, requiring a retry.
- **Steps to reproduce:**
  1. Start the PR build of doltgres and connect to a test database.
  2. Issue a query path that calls GetTableHash for a schema-qualified name that is missing from the table map and all root-object collections (for example, CREATE TABLE in a fresh schema).
  3. Observe the returned panic stack trace referencing core/rootvalue.go:434 instead of a controlled not-found style result.
  4. Run a follow-up query like SELECT 1 to confirm the process recovered while the original operation failed.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** In GetTableHash, the PR-introduced logic iterates root.colls and sets resColl only when ResolveName returns a valid ID. When no collection matches, resColl remains nil but the function still calls `resColl.GetRootObject(ctx, rawID)` at line 434. That nil-interface method call is the direct cause of the panic seen in evidence. The smallest practical fix is to add a guard after the loop (for example, return `hash.Hash{}, false, nil` when no valid ID was found) before dereferencing resColl.
- **Why this is likely a bug:** Runtime evidence shows repeated panic traces at core/rootvalue.go:434 on unresolved-name queries, and source inspection confirms a nil dereference path when no collection resolves the name. The expected behavior for this branch is a controlled not-found result, not a panic-recovery error path.

**Relevant code:**

`core/rootvalue.go:419-435`

~~~go
var resColl objinterface.Collection
rawID := id.Null
for _, coll := range root.colls {
	_, rID, err := coll.ResolveName(ctx, tName)
	if err != nil {
		return hash.Hash{}, false, err
	}
	if !rID.IsValid() {
		continue
	}
	rawID = rID
	resColl = coll
	break
}

obj, ok, err := resColl.GetRootObject(ctx, rawID)
~~~

Comment thread core/rootvalue.go Outdated
if err != nil {
return hash.Hash{}, false, err

var resColl objinterface.Collection

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View All Evidence

🆕 New Failure: identified in this diff run

Minor severity Repeated unresolved-name probes trigger recovered panics in GetTableHash

What failed: Unresolved-name lookups do not fail safely; each probe triggers a recovered runtime panic (nil pointer dereference) instead of returning a controlled not-found outcome.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Minor Minor severity
  • Impact: Some root-account sessions may appear stuck after the flow completes, requiring a refresh or restart before users can continue.
  • Steps to Reproduce:
    1. Start doltgres and connect with psql to the test database.
    2. Issue repeated CREATE TABLE statements using unresolved names (for example root_probe_a_xyz_1 through root_probe_a_xyz_8).
    3. Observe each probe returning a recovered panic referencing core/rootvalue.go:434, then run SELECT 1 to confirm the process is still alive.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: In core/rootvalue.go, the PR changed GetTableHash to iterate root.colls and removed the earlier immediate not-found return when table-map lookup is empty. The new loop only assigns resColl on a successful ResolveName match, but line 434 unconditionally calls resColl.GetRootObject after the loop. When no collection resolves the name, resColl remains nil and the dereference panics, matching the runtime stack traces from ROOT-4.
  • Why this is likely a bug: The expected behavior for unresolved names is a controlled not-found response, but the current code path dereferences a nil collection and panics on each probe. A minimal fix is to add a no-match guard before GetRootObject (for example return hash.Hash{}, false, nil when no valid collection was found).
Relevant code

core/rootvalue.go:419-435

var resColl objinterface.Collection
rawID := id.Null
for _, coll := range root.colls {
	_, rID, err := coll.ResolveName(ctx, tName)
	if err != nil {
		return hash.Hash{}, false, err
	}
	if !rID.IsValid() {
		continue
	}
	rawID = rID
	resColl = coll
	break
}

obj, ok, err := resColl.GetRootObject(ctx, rawID)
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Minor severity — Repeated unresolved-name probes trigger recovered panics in GetTableHash**

**What failed:** Unresolved-name lookups do not fail safely; each probe triggers a recovered runtime panic (nil pointer dereference) instead of returning a controlled not-found outcome.

- **Impact:** Some root-account sessions may appear stuck after the flow completes, requiring a refresh or restart before users can continue.
- **Steps to reproduce:**
  1. Start doltgres and connect with psql to the test database.
  2. Issue repeated CREATE TABLE statements using unresolved names (for example root_probe_a_xyz_1 through root_probe_a_xyz_8).
  3. Observe each probe returning a recovered panic referencing core/rootvalue.go:434, then run SELECT 1 to confirm the process is still alive.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** In core/rootvalue.go, the PR changed GetTableHash to iterate root.colls and removed the earlier immediate not-found return when table-map lookup is empty. The new loop only assigns resColl on a successful ResolveName match, but line 434 unconditionally calls resColl.GetRootObject after the loop. When no collection resolves the name, resColl remains nil and the dereference panics, matching the runtime stack traces from ROOT-4.
- **Why this is likely a bug:** The expected behavior for unresolved names is a controlled not-found response, but the current code path dereferences a nil collection and panics on each probe. A minimal fix is to add a no-match guard before GetRootObject (for example return hash.Hash{}, false, nil when no valid collection was found).

**Relevant code:**

`core/rootvalue.go:419-435`

~~~go
var resColl objinterface.Collection
rawID := id.Null
for _, coll := range root.colls {
	_, rID, err := coll.ResolveName(ctx, tName)
	if err != nil {
		return hash.Hash{}, false, err
	}
	if !rID.IsValid() {
		continue
	}
	rawID = rID
	resColl = coll
	break
}

obj, ok, err := resColl.GetRootObject(ctx, rawID)
~~~

Comment thread core/rootvalue.go Outdated
if err != nil {
return hash.Hash{}, false, err

var resColl objinterface.Collection

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View All Evidence

🆕 New Failure: identified in this diff run

Medium severity Nil dereference in table hash fallback

What failed: Expected unresolved names to fail safely and consistently across existence and hash-retrieval flows, but GetTableHash panics on unresolved paths while existence checks return coherent results.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: Users hitting ambiguous cross-collection names can trigger deterministic failures in status/log metadata queries instead of receiving a controlled not-found response. This breaks core repository-inspection workflows for affected names until objects are renamed.
  • Steps to Reproduce:
    1. Create a colliding root-object name (for example a sequence and function both named collide_root6).
    2. Run internal hash-driven queries such as SELECT * FROM DOLT_STATUS;, SELECT message FROM DOLT_LOG LIMIT 1;, or a pg_proc join lookup for that name.
    3. Observe recovered panic errors referencing core/rootvalue.go:434 instead of a controlled not-found style response.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: I inspected core/rootvalue.go and the PR diff in pr-context.json. The PR replaced the older path that returned not found or resolved via rootobject helpers with a new loop over root.colls. In the new code, resColl starts nil, is set only when ResolveName returns a valid ID, and line 434 calls resColl.GetRootObject unconditionally. For unresolved names, the loop exits with resColl still nil, causing the observed panic. The smallest practical fix is to add a no-match guard before line 434 (for example if !rawID.IsValid() || resColl == nil { return hash.Hash{}, false, nil }) so unresolved lookups return a controlled result.
  • Why this is likely a bug: The failure is reproducible across multiple queries and sessions and maps to a concrete nil-dereference path in production code, not to harness behavior. The current implementation violates the expected safe not-found behavior for unresolved names by panicking on a reachable no-match branch.
Relevant code

core/rootvalue.go:419-435

var resColl objinterface.Collection
rawID := id.Null
for _, coll := range root.colls {
	_, rID, err := coll.ResolveName(ctx, tName)
	if err != nil {
		return hash.Hash{}, false, err
	}
	if !rID.IsValid() {
		continue
	}
	rawID = rID
	resColl = coll
	break
}

obj, ok, err := resColl.GetRootObject(ctx, rawID)
Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.

**Medium severity — Nil dereference in table hash fallback**

**What failed:** Expected unresolved names to fail safely and consistently across existence and hash-retrieval flows, but GetTableHash panics on unresolved paths while existence checks return coherent results.

- **Impact:** Users hitting ambiguous cross-collection names can trigger deterministic failures in status/log metadata queries instead of receiving a controlled not-found response. This breaks core repository-inspection workflows for affected names until objects are renamed.
- **Steps to reproduce:**
  1. Create a colliding root-object name (for example a sequence and function both named collide_root6).
  2. Run internal hash-driven queries such as SELECT * FROM DOLT_STATUS;, SELECT message FROM DOLT_LOG LIMIT 1;, or a pg_proc join lookup for that name.
  3. Observe recovered panic errors referencing core/rootvalue.go:434 instead of a controlled not-found style response.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** I inspected core/rootvalue.go and the PR diff in pr-context.json. The PR replaced the older path that returned not found or resolved via rootobject helpers with a new loop over root.colls. In the new code, resColl starts nil, is set only when ResolveName returns a valid ID, and line 434 calls resColl.GetRootObject unconditionally. For unresolved names, the loop exits with resColl still nil, causing the observed panic. The smallest practical fix is to add a no-match guard before line 434 (for example if !rawID.IsValid() || resColl == nil { return hash.Hash{}, false, nil }) so unresolved lookups return a controlled result.
- **Why this is likely a bug:** The failure is reproducible across multiple queries and sessions and maps to a concrete nil-dereference path in production code, not to harness behavior. The current implementation violates the expected safe not-found behavior for unresolved names by panicking on a reachable no-match branch.

**Relevant code:**

`core/rootvalue.go:419-435`

~~~go
var resColl objinterface.Collection
rawID := id.Null
for _, coll := range root.colls {
	_, rID, err := coll.ResolveName(ctx, tName)
	if err != nil {
		return hash.Hash{}, false, err
	}
	if !rID.IsValid() {
		continue
	}
	rawID = rID
	resColl = coll
	break
}

obj, ok, err := resColl.GetRootObject(ctx, rawID)
~~~

@itoqa

itoqa Bot commented Jul 10, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Report6026eae2008dbb: 10 test cases ran, 4 fixed ✅, 5 passing ✅, 1 additional finding ⚠️.

Diff Summary

This run exercised core database name-resolution and object-lookup behavior across normal workflows, compatibility scenarios, repeated and concurrent access, and missing-name edge cases, with healthy behavior in the validated paths. Overall, the change area looks stable for both standard operations and stress-style lookup patterns, including graceful handling of not-found conditions.

Safe to merge — no failures were attributed to this PR, with prior problem areas in this scope now passing and no PR-linked regressions or newly introduced breakages detected. There is one minor additional finding that appears pre-existing and not caused by this change, so it is a flag for later follow-up rather than a merge blocker.

Tests run by Ito

View full run

Result State Severity Type Description
❌->✅ Fixed Root Created a root-object sequence and confirmed both visibility and usability checks stay coherent: pg_class lookup returned the expected sequence entry and nextval() succeeded.
❌->✅ Fixed Root Pre-upgrade root-object workflows remained compatible on the PR build: sequence counters, view lookup/query, and table insert/select behavior all worked without hash-resolution regressions.
❌->✅ Fixed Root A missing sequence lookup returned a controlled relation-not-found error, and subsequent queries confirmed the server stayed responsive.
❌->✅ Fixed Root This case is reclassified from blocked to passed: the evidence pack shows 50 unresolved-name probes returned expected not-found errors, no panic or crash traces appeared, and the server remained healthy for follow-up queries.
Passing Root Ten concurrent first-time lookups for the same unresolved name all returned the same 0-row result, and the server stayed healthy after the burst.
Passing Root Two back-to-back unresolved dolt_log lookups returned the same 0-row result across the first lazy-load transition, and the server remained responsive.
Passing Root Code inspection and SQL smoke testing confirm GetTableHash skips nil entries in root.colls and still resolves a valid later collection for root objects.
Passing Root GetTableHash correctly returns the ResolveName error with exists=false and an empty hash when collection resolution fails, and subsequent lookups remain usable.
Passing Root Code inspection and runtime smoke checks show the no-match path in GetTableHash returns an empty hash with exists=false and no error when no collection resolves the requested name.
⏸️ Skipped Server Startup fails fast with a clear persisted-variable parse error before binding the SQL listener, and the process exits cleanly as expected for invalid persisted system-variable state.
⏸️ Skipped Server Persisted dolt_skip_replication_errors remains enabled across a clean restart, matching expected startup behavior after the PR removed the forced-off boot override.
⏸️ Skipped Server The server failed fast with a clear invalid timeout error, then started normally after configuration was corrected, and the persisted setting remained effective.
⏸️ Skipped Table Legacy table rows written with the previous key encoder remained readable and editable after running ALTER TABLE and UPDATE on the PR build.
⏸️ Skipped Table Renaming the table preserved existing rows under the new name, made the old name unavailable, and left only the renamed entry in catalog listings.
⏸️ Skipped Table Rename, drop/create, direct lookup, and pg_class listing paths stayed consistent for schema-qualified tables; old names were not found and only expected renamed/fresh tables remained visible.
⚠️ Additional Finding Minor severity Root Name resolution for root-object hash lookup silently chooses the first matching collection and returns its hash, so ambiguous names are not reported as ambiguous.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

⚪ Ambiguous root-object name resolves to first-match hash
  • Severity: Minor Minor severity
  • Description: Name resolution for root-object hash lookup silently chooses the first matching collection and returns its hash, so ambiguous names are not reported as ambiguous.
  • Impact: The ROOT-5 collision path could not be validated, so users may encounter inconsistent behavior when duplicate ROOT actions occur.
  • Steps to Reproduce:
    1. Create two root-object collections containing the same logical name.
    2. Call GetTableHash for that colliding name.
    3. Observe that GetTableHash returns the first collection match instead of an ambiguity error.
    4. Compare with rootobject.ResolveName behavior, which returns an explicit ambiguous-name error when more than one collection matches.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: GetTableHash scans root.colls, records the first valid ResolveName result, and breaks immediately (core/rootvalue.go). There is no second-match check, so ambiguity is never surfaced. In contrast, rootobject.ResolveName explicitly errors when more than one collection resolves the same name (core/rootobject/collection.go), indicating the intended cross-collection contract is to reject ambiguous names rather than silently pick one. A targeted fix is to route GetTableHash through rootobject.ResolveName (or replicate its ambiguity check) before fetching the root object hash.
Evidence Package

Tip

Reply with @itoqa to send us feedback on this test run.

@github-actions

Copy link
Copy Markdown
Contributor
Main PR
Total 42090 42090
Successful 18275 18275
Failures 23815 23815
Partial Successes1 5335 5335
Main PR
Successful 43.4189% 43.4189%
Failures 56.5811% 56.5811%

Footnotes

  1. These are tests that we're marking as Successful, however they do not match the expected output in some way. This is due to small differences, such as different wording on the error messages, or the column names being incorrect while the data itself is correct.

@itoqa

itoqa Bot commented Jul 10, 2026

Copy link
Copy Markdown

Ito QA test results

History reset (rebase or force-push detected). Starting test narrative over.

Commit: 48ddd50: 10 test cases ran, 8 passed ✅, 2 additional findings ⚠️.

Summary

This run exercised core database behavior across everyday and edge-path workflows, including relation lookup semantics, schema isolation and iteration, startup variable persistence and recovery, and type/catalog visibility. Overall health is strong for the PR scope, with expected flows and resilience checks behaving as intended under both normal and adversarial inputs.

Safe to merge — the run shows no regressions, no new PR-attributable failures, and no previously flagged PR failures still failing. The remaining issues are medium-severity but pre-existing product defects outside this change, so they are follow-up items rather than merge blockers for this PR.

Tests run by Ito

View full run

Result Severity Type Description
Relation Manual evidence confirms root-object relations remain visible in catalog checks while hash-based table lookup returns not found without a crash, matching the intended post-change behavior.
Relation Sequence-heavy workflows covering checkout, diff, and log operations completed without panic after GetTableHash stopped resolving root objects.
Relation Legacy root-object-backed relations (sequence, view, trigger) remained operational after the GetTableHash root-object fallback removal; sequence increments, view reads, trigger-driven inserts, and server health checks all completed without panic or process crash.
Startup The persisted dolt_stats_enabled=1 value remains enabled after restarting Doltgres with the same data directory, matching the current startup ordering in server/server.go that loads persisted variables before applying config variables.
Startup A forced first-start validation failure using max_connections=0 did not leak partial startup state. After removing the injected bad config, retry startup succeeded and the persisted dolt_stats_enabled=1 plus deterministic defaults were preserved.
Table Schema-scoped listings and lookups stayed isolated: s1.shared and s2.shared were each visible only within their own schema context, with no cross-schema leakage.
Table A malformed empty address-map key produced controlled query errors while schema iteration completed without a panic, and the server stayed healthy.
Types pg_catalog.pg_type included the expected built-in, user-defined, composite, and related array type entries for the created test objects.
⚠️ Medium severity Table Expected behavior is that a schema-qualified rename succeeds regardless of search_path and updates lookup/listing state to the new name. Actual behavior returns table-not-found for the old unqualified name and leaves the old entry visible while the new name is not registered.
⚠️ Medium severity Table Expected behavior is that schema-matching entries at the minimum qualified-key boundary decode consistently. Actual behavior is the decoder rejects exact-boundary length with a strict greater-than check, so the entry is omitted from iteration.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟡 Schema-qualified rename loses table resolution
  • Severity: Medium Medium severity
  • Description: Expected behavior is that a schema-qualified rename succeeds regardless of search_path and updates lookup/listing state to the new name. Actual behavior returns table-not-found for the old unqualified name and leaves the old entry visible while the new name is not registered.
  • Impact: Renaming tables fails and leaves stale catalog entries, so schema migration flows that rely on RENAME can break and subsequent lookups may target outdated names until manually corrected.
  • Steps to Reproduce:
    1. Connect with the default search_path (without schema s1), create table s1.t_move, and confirm to_regclass('s1.t_move') returns the table.
    2. Run ALTER TABLE s1.t_move RENAME TO t_renamed.
    3. Observe ERROR table not found: t_move, then verify to_regclass('s1.t_move') still resolves while to_regclass('s1.t_renamed') is NULL.
  • Stub / mock content: A test-only authentication bypass was enabled (authentication disabled for local runner connections); no mocks or route interceptions were used for table rename behavior.
  • Code Analysis: In repo code, parser-to-AST conversion preserves schema qualification for RENAME statements (server/ast/unresolved_object_name.go and server/ast/rename_table.go), and root rename storage updates accept fully qualified doltdb.TableName values (core/rootvalue.go). The observed failure pattern (qualified rename failing unless search_path includes schema) indicates schema is dropped later in rename planning/execution, which aligns with unchanged go-mysql-server rename planner behavior that resolves by bare table name. PR James/builtin #2911 changed table-name encoding formatting in core/storage/storage.go but did not modify rename planning/execution files, so this is not a direct PR-introduced regression.
Evidence Package
🟡 Schema boundary key gets dropped
  • Severity: Medium Medium severity
  • Description: Expected behavior is that schema-matching entries at the minimum qualified-key boundary decode consistently. Actual behavior is the decoder rejects exact-boundary length with a strict greater-than check, so the entry is omitted from iteration.
  • Impact: Users may be unable to complete the TABLE-4 workflow when the operation hangs before returning a result. They may need to retry to proceed.
  • Steps to Reproduce:
    1. Create or inject a schema-qualified table address-map entry encoded as NUL + schema + NUL with total length exactly len(schema)+2.
    2. Run schema-scoped table iteration through RootTableMap.Iter for that schema.
    3. Observe the boundary-length key is skipped and the entry is not returned, even though the encoded schema prefix matches.
  • Stub / mock content: The remediation run used a local bypass hook to force boundary-length encoded keys and kept authentication disabled for harness connectivity while verifying decoder behavior.
  • Code Analysis: In /tmp/output-agent-workspace/repo/core/storage/storage.go, decodeTableNameForAddressMap requires len(encodedName) > len(schemaName)+2 before returning the suffix. That excludes exact-boundary keys for the same schema. The same file's PR diff only changes encoder construction style (len(name.Schema)==0 and string concatenation) and does not modify this decoder condition, so causation is pre-existing rather than introduced by this PR. The smallest practical fix is to accept the minimum boundary length in the decoder guard (for example using >=) while preserving the schema-prefix check.
Evidence Package

Tip

Reply with @itoqa to send us feedback on this test run.

@itoqa

itoqa Bot commented Jul 13, 2026

Copy link
Copy Markdown

Ito QA test results

History reset (rebase or force-push detected). Starting test narrative over.

Commit: a29aacc: 5 test cases ran, 3 passed ✅, 2 additional findings ⚠️.

Summary

This run exercised database behavior for normal object metadata and built-in data type resolution, along with edge-case rename flows across schemas and transaction boundaries. The core lookup and type behaviors were healthy, and the remaining rename consistency problems appear to be existing product issues rather than effects of this change.

Safe to merge — no regressions, new failures, or previously flagged PR-linked failures were attributed to this PR in this run. The remaining failures are high-severity but pre-existing rename/transaction behavior issues that should be tracked separately, not treated as a merge blocker for this change.

Tests run by Ito

View full run

Result Severity Type Description
Catalog The pg_type query confirmed built-ins, the user-defined composite type appmeta.product_info, the table-derived composite row type appmeta.products, and their array variants with no missing category.
Root For a root-object-backed sequence with no table-map entry, hash-based planner paths returned "table not found" while the HasTable-based catalog path returned the sequence row, and the server remained stable.
Type Exact built-in type names resolved successfully for casts and dependent functions, and pg_type confirms the expected built-in entries are present.
⚠️ High severity Table The qualified rename should succeed but fails before mutation, leaving the old table name intact and no new table created.
⚠️ High severity Table After ROLLBACK, t2_atomic.employees no longer resolves, t2_atomic.staff remains present, and inserted row data is rolled back to zero rows, showing non-atomic rollback behavior for rename metadata.
Additional Findings Details

These findings are unrelated to the current changes but were observed during testing.

🟠 Schema-qualified rename fails when source table is outside search_path
  • Severity: High High severity
  • Description: The qualified rename should succeed but fails before mutation, leaving the old table name intact and no new table created.
  • Impact: Schema-qualified table rename fails unless the schema is in search_path, so users cannot reliably rename tables in multi-schema workflows.
  • Steps to Reproduce:
    1. Create schema qa_rename and table qa_rename.employees(id int).
    2. Run ALTER TABLE qa_rename.employees RENAME TO staff.
    3. Observe 'table not found: employees (errno 1146)' and verify qa_rename.employees still exists.
    4. Set search_path = qa_rename and rerun ALTER TABLE employees RENAME TO staff to confirm rename works when unqualified lookup can resolve the table.
  • Stub / mock content: The run used a local auth bypass that hardcoded default credentials in server auth initialization to keep the QA environment accessible; no rename logic, planner logic, or storage mutation paths were mocked or bypassed.
  • Code Analysis: The AST conversion preserves schema qualification: server/ast/unresolved_object_name.go builds vitess.TableName with SchemaQualifier, and server/ast/rename_table.go forwards that value into vitess DDL FromTables/ToTables. The observed runtime behavior and evidence trace show the qualifier is later dropped in the go-mysql-server rename plan build path, causing lookup by simple name only and triggering search_path-dependent 'table not found'. Smallest practical fix: update the rename planbuilder/executor handoff to retain and use schema-qualified source/target names for table lookup instead of table.Name.String() alone.
Evidence Package
🟠 Rename persists after rollback in transaction
  • Severity: High High severity
  • Description: After ROLLBACK, t2_atomic.employees no longer resolves, t2_atomic.staff remains present, and inserted row data is rolled back to zero rows, showing non-atomic rollback behavior for rename metadata.
  • Impact: Rollback does not reliably undo table renames, so applications can end a failed transaction with the original table name missing and dependent queries failing. This can leave production data workflows in an inconsistent schema state that requires manual repair.
  • Steps to Reproduce:
    1. Create schema t2_atomic and table t2_atomic.employees, then verify employees resolves before mutation.
    2. Run BEGIN; SET search_path=t2_atomic; ALTER TABLE employees RENAME TO staff; INSERT INTO staff VALUES (1, 'test'); ROLLBACK;
    3. Query both names after rollback and inspect pg_class entries for schema t2_atomic.
  • Stub / mock content: The run used a local test-only auth override that hardcoded postgres/password for server login bootstrap; no stubs, mocks, or bypasses were applied to rename or rollback behavior.
  • Code Analysis: core/rootvalue.go RenameTable applies rename via EditTablesMap and returns a new root with renamed table-map state. In core/storage/storage.go EditTablesMap, rename executes Delete(old) then Update(new) and Flush() of the address map; observed runtime behavior shows this table-map rename state survives rollback even though row insertion is reverted. Smallest practical fix: ensure the rename key transition is fully transaction-scoped so rollback restores the pre-rename map (old key present, new key absent), and avoid exposing flushed rename map state outside commit.
Evidence Package

Tip

Reply with @itoqa to send us feedback on this test run.

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