Skip to content

optimize cast logic#2892

Open
jycor wants to merge 5 commits into
mainfrom
james/cast
Open

optimize cast logic#2892
jycor wants to merge 5 commits into
mainfrom
james/cast

Conversation

@jycor

@jycor jycor commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor
Main PR
covering_index_scan_postgres 1848.23/s 1829.77/s -1.0%
groupby_scan_postgres 135.75/s 134.53/s -0.9%
index_join_postgres 646.44/s 642.16/s -0.7%
index_join_scan_postgres 790.73/s 784.16/s -0.9%
index_scan_postgres 24.59/s 24.44/s -0.7%
oltp_delete_insert_postgres 776.35/s 759.25/s -2.3%
oltp_insert 659.16/s 674.70/s +2.3%
oltp_point_select 2830.23/s 2902.92/s +2.5%
oltp_read_only 2869.16/s 2920.17/s +1.7%
oltp_read_write 2297.44/s 2304.24/s +0.2%
oltp_update_index 695.49/s 697.07/s +0.2%
oltp_update_non_index 734.59/s 715.06/s -2.7%
oltp_write_only 1702.83/s 1690.25/s -0.8%
select_random_points 1852.39/s 1867.17/s +0.7%
select_random_ranges 1073.68/s ${\color{lightgreen}1202.68/s}$ ${\color{lightgreen}+12.0\%}$
table_scan_postgres 23.29/s 23.28/s -0.1%
types_delete_insert_postgres 735.39/s 747.18/s +1.6%
types_table_scan_postgres 10.11/s 10.05/s -0.6%

@itoqa

itoqa Bot commented Jul 1, 2026

Copy link
Copy Markdown

Ito QA test results
Commit: e4242b9: 5 test cases ran, 4 failed ❌, 1 passed ✅, 0 additional findings ⚠️.

Summary

This run exercised SQL behavior around custom type conversion setup and lookup, including normal coercion paths plus long-identifier edge cases and persistence round-trips, and also checked function overload resolution behavior. Overall health is mixed: overload selection remains stable, but the cast-related paths show regressions where created conversions do not reliably remain usable.

Not safe to merge yet — this PR appears to introduce multiple medium-severity regressions in the same changed area, breaking core custom-cast behavior after creation and under long-name conditions. Because these are functional correctness issues in user-facing conversion workflows rather than isolated pre-existing noise, merge risk is high until the encoding/lookup consistency is fixed.

Tests run by Ito

View full run

Result Severity Type Description
Medium severity Encoding The cast registration write path acknowledges success, but subsequent cast retrieval paths do not resolve that same cast identity for execution/catalog access.
Medium severity Encoding Cast IDs are encoded with fixed one-byte length fields in NewCast, so long-segment IDs can overflow and break lookup/catalog identity consistency.
Medium severity Overflow The system preserves raw cast ID bytes in storage, but semantic decode uses wrapped uint8 segment lengths, so cast identity is corrupted and lookup paths such as CastIDToTableName cannot reliably resolve the original cast.
Minor severity Overflow Expected long-identifier casts to be uniquely encoded and resolvable before and after restart, but casts with type IDs above 255 bytes fail at creation because decoded source/target identity is truncated.
Resolution The overload resolver consistently selected the exact-match or implicitly compatible function signatures and rejected non-convertible DATE input with a deterministic does-not-exist error.

Tip

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

Comment thread core/id/id_wrappers.go
}
return Cast(NewId(Section_Cast, string(sourceType), string(targetType)))

return Cast(

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

Medium severity User-defined cast cannot be resolved after successful CREATE CAST

What failed: The cast registration write path acknowledges success, but subsequent cast retrieval paths do not resolve that same cast identity for execution/catalog access.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: Users cannot reliably use user-defined casts after creation because the cast is not discoverable by lookup or coercion paths. Workflows that depend on custom type coercion fail until the ID parity bug is fixed.
  • Steps to Reproduce:
    1. Create source and target user-defined types and a conversion function.
    2. Run CREATE CAST (source AS target) WITH FUNCTION ... AS ASSIGNMENT and confirm it returns success.
    3. Attempt coercion with SELECT ROW(42)::enc1_x::enc1_y; and observe cast does not exist.
    4. Query pg_catalog.pg_cast for the created cast and observe no matching discoverable cast entry for the new pair.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: NewCast now bypasses NewId and manually emits first-format bytes with uint8(len(...)) in core/id/id_wrappers.go lines 94-104. The cast system uses this encoded key for persistence and lookup (server/node/create_cast.go line 86 and core/casts/collection.go lines 422-427), while catalog/runtime consumers decode source/target via segment extraction (server/tables/pgcatalog/pg_cast.go lines 117-119 and core/id/id_wrappers.go lines 258-264). The changed encoding path is therefore a direct practical cause of the observed write/read mismatch for cast discoverability.
  • Why this is likely a bug: The same source/target pair succeeds during CREATE CAST but is immediately unresolved during coercion, which indicates an internal cast ID consistency defect rather than expected SQL behavior. The failure aligns with the PR's changed cast-ID encoding path and impacts core cast retrieval semantics.
Relevant code

core/id/id_wrappers.go:94-104

return Cast(
	string(
		[]byte{
			uint8(Section_Cast),
			2,
			uint8(len(sourceType)),
			uint8(len(targetType)),
		}) +
		string(sourceType) +
		string(targetType),
)

server/node/create_cast.go:85-87

newCast := casts.Cast{
	ID:       id.NewCast(c.Source.ID, c.Target.ID),
	CastType: c.Type,
}

server/tables/pgcatalog/pg_cast.go:117-119

return sql.Row{
	cast.ID.AsId(),
	cast.ID.SourceType().AsId(),
	cast.ID.TargetType().AsId(),
...
}
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 — User-defined cast cannot be resolved after successful CREATE CAST**

**What failed:** The cast registration write path acknowledges success, but subsequent cast retrieval paths do not resolve that same cast identity for execution/catalog access.

- **Impact:** Users cannot reliably use user-defined casts after creation because the cast is not discoverable by lookup or coercion paths. Workflows that depend on custom type coercion fail until the ID parity bug is fixed.
- **Steps to reproduce:**
  1. Create source and target user-defined types and a conversion function.
  2. Run CREATE CAST (source AS target) WITH FUNCTION ... AS ASSIGNMENT and confirm it returns success.
  3. Attempt coercion with `SELECT ROW(42)::enc1_x::enc1_y;` and observe `cast does not exist`.
  4. Query pg_catalog.pg_cast for the created cast and observe no matching discoverable cast entry for the new pair.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** `NewCast` now bypasses `NewId` and manually emits first-format bytes with `uint8(len(...))` in `core/id/id_wrappers.go` lines 94-104. The cast system uses this encoded key for persistence and lookup (`server/node/create_cast.go` line 86 and `core/casts/collection.go` lines 422-427), while catalog/runtime consumers decode source/target via segment extraction (`server/tables/pgcatalog/pg_cast.go` lines 117-119 and `core/id/id_wrappers.go` lines 258-264). The changed encoding path is therefore a direct practical cause of the observed write/read mismatch for cast discoverability.
- **Why this is likely a bug:** The same source/target pair succeeds during CREATE CAST but is immediately unresolved during coercion, which indicates an internal cast ID consistency defect rather than expected SQL behavior. The failure aligns with the PR's changed cast-ID encoding path and impacts core cast retrieval semantics.

**Relevant code:**

`core/id/id_wrappers.go:94-104`

~~~go
return Cast(
	string(
		[]byte{
			uint8(Section_Cast),
			2,
			uint8(len(sourceType)),
			uint8(len(targetType)),
		}) +
		string(sourceType) +
		string(targetType),
)
~~~

`server/node/create_cast.go:85-87`

~~~go
newCast := casts.Cast{
	ID:       id.NewCast(c.Source.ID, c.Target.ID),
	CastType: c.Type,
}
~~~

`server/tables/pgcatalog/pg_cast.go:117-119`

~~~go
return sql.Row{
	cast.ID.AsId(),
	cast.ID.SourceType().AsId(),
	cast.ID.TargetType().AsId(),
...
}
~~~

Comment thread core/id/id_wrappers.go
}
return Cast(NewId(Section_Cast, string(sourceType), string(targetType)))

return Cast(

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

Medium severity Cast ID encoding breaks long type identifier handling

What failed: Cast IDs are encoded with fixed one-byte length fields in NewCast, so long-segment IDs can overflow and break lookup/catalog identity consistency.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: Users cannot rely on custom cast definitions because CREATE CAST appears to succeed but casts are missing from the catalog and runtime conversions fail.
  • Steps to Reproduce:
    1. Create source and target types with long names so their internal type IDs can exceed one-byte segment limits.
    2. Create a cast between those types with CREATE CAST ... WITH FUNCTION ....
    3. Query pg_catalog.pg_cast and execute a coercion using that cast.
    4. Observe cast identity/lookup mismatch (missing catalog entry or cast-not-found behavior) when ID decoding does not round-trip.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: In core/id/id_wrappers.go, NewCast now manually builds first-format ID bytes and stores segment lengths as uint8. In core/id/id.go, NewId explicitly switches to a second format when any segment length is greater than 255. The PR change bypasses that safeguard specifically for cast IDs, creating a direct defect path for long source/target type IDs.
  • Why this is likely a bug: The shared ID constructor already defines the required fallback for segments above 255 bytes, but NewCast no longer uses it and can emit truncated length metadata. The smallest practical fix is to restore NewCast to NewId(Section_Cast, string(sourceType), string(targetType)) or mirror the same second-format fallback before writing one-byte lengths.
Relevant code

core/id/id_wrappers.go:94-104

return Cast(
	string(
		[]byte{
			uint8(Section_Cast),
			2,
			uint8(len(sourceType)),
			uint8(len(targetType)),
		}) +
		string(sourceType) +
		string(targetType),
)

core/id/id.go:109-113

segmentLength := len(segment)
if segmentLength > 255 {
	return newIdSecondFormat(section, data)
}
buf.WriteByte(uint8(segmentLength))
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 — Cast ID encoding breaks long type identifier handling**

**What failed:** Cast IDs are encoded with fixed one-byte length fields in `NewCast`, so long-segment IDs can overflow and break lookup/catalog identity consistency.

- **Impact:** Users cannot rely on custom cast definitions because CREATE CAST appears to succeed but casts are missing from the catalog and runtime conversions fail.
- **Steps to reproduce:**
  1. Create source and target types with long names so their internal type IDs can exceed one-byte segment limits.
  2. Create a cast between those types with `CREATE CAST ... WITH FUNCTION ...`.
  3. Query `pg_catalog.pg_cast` and execute a coercion using that cast.
  4. Observe cast identity/lookup mismatch (missing catalog entry or cast-not-found behavior) when ID decoding does not round-trip.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** In `core/id/id_wrappers.go`, `NewCast` now manually builds first-format ID bytes and stores segment lengths as `uint8`. In `core/id/id.go`, `NewId` explicitly switches to a second format when any segment length is greater than 255. The PR change bypasses that safeguard specifically for cast IDs, creating a direct defect path for long source/target type IDs.
- **Why this is likely a bug:** The shared ID constructor already defines the required fallback for segments above 255 bytes, but `NewCast` no longer uses it and can emit truncated length metadata. The smallest practical fix is to restore `NewCast` to `NewId(Section_Cast, string(sourceType), string(targetType))` or mirror the same second-format fallback before writing one-byte lengths.

**Relevant code:**

`core/id/id_wrappers.go:94-104`

~~~go
return Cast(
	string(
		[]byte{
			uint8(Section_Cast),
			2,
			uint8(len(sourceType)),
			uint8(len(targetType)),
		}) +
		string(sourceType) +
		string(targetType),
)
~~~

`core/id/id.go:109-113`

~~~go
segmentLength := len(segment)
if segmentLength > 255 {
	return newIdSecondFormat(section, data)
}
buf.WriteByte(uint8(segmentLength))
~~~

Comment thread core/id/id_wrappers.go
}
return Cast(NewId(Section_Cast, string(sourceType), string(targetType)))

return Cast(

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

Minor severity Long type cast IDs break lookup

What failed: Expected long-identifier casts to be uniquely encoded and resolvable before and after restart, but casts with type IDs above 255 bytes fail at creation because decoded source/target identity is truncated.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Minor Minor severity
  • Impact: Users who define very long type identifiers (over 255 bytes) cannot create casts because lookup resolves to an invalid identifier. Typical schemas with shorter identifiers continue to work.
  • Steps to Reproduce:
    1. Create two types where one generated type ID exceeds 255 bytes and attempt CREATE CAST from int4 to that type.
    2. Observe CREATE CAST fails with root returned table (pg_catalog)|(int4)|()|() but it could not be found.
    3. Create a cast to a similar type whose ID is below 256 bytes and observe it succeeds, then restart and retry the long-ID cast to confirm the same failure persists.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: In core/id/id_wrappers.go, NewCast now manually writes segment lengths with uint8(len(sourceType)) and uint8(len(targetType)), which overflows for IDs >255 bytes. core/id/id.go shows NewId would otherwise switch to newIdSecondFormat when any segment exceeds 255, but NewCast bypasses that guard. core/casts/collection.go builds lookup table names from castID.SourceType()/TargetType(), so truncated decode yields the observed (pg_catalog)|(int4)|()|() lookup miss. The failing path is directly in PR-changed code.
  • Why this is likely a bug: Runtime evidence consistently fails only when IDs cross the 255-byte boundary, and source inspection shows deterministic uint8 truncation in the changed encoding path. A targeted fix is to route NewCast back through NewId (or equivalent overflow-safe encoding) so long segments use the second format.
Relevant code

core/id/id_wrappers.go:94-103

return Cast(string([]byte{uint8(Section_Cast), 2, uint8(len(sourceType)), uint8(len(targetType))}) + string(sourceType) + string(targetType))

core/id/id.go:102-113

if len(data) > 255 { return newIdSecondFormat(section, data) } ... if segmentLength > 255 { return newIdSecondFormat(section, data) }

core/casts/collection.go:701-706

name := fmt.Sprintf(`(%s)|(%s)|(%s)|(%s)`, castID.SourceType().SchemaName(), castID.SourceType().TypeName(), castID.TargetType().SchemaName(), castID.TargetType().TypeName())
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 — Long type cast IDs break lookup**

**What failed:** Expected long-identifier casts to be uniquely encoded and resolvable before and after restart, but casts with type IDs above 255 bytes fail at creation because decoded source/target identity is truncated.

- **Impact:** Users who define very long type identifiers (over 255 bytes) cannot create casts because lookup resolves to an invalid identifier. Typical schemas with shorter identifiers continue to work.
- **Steps to reproduce:**
  1. Create two types where one generated type ID exceeds 255 bytes and attempt CREATE CAST from int4 to that type.
  2. Observe CREATE CAST fails with `root returned table `(pg_catalog)|(int4)|()|()` but it could not be found`.
  3. Create a cast to a similar type whose ID is below 256 bytes and observe it succeeds, then restart and retry the long-ID cast to confirm the same failure persists.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** In `core/id/id_wrappers.go`, `NewCast` now manually writes segment lengths with `uint8(len(sourceType))` and `uint8(len(targetType))`, which overflows for IDs >255 bytes. `core/id/id.go` shows `NewId` would otherwise switch to `newIdSecondFormat` when any segment exceeds 255, but `NewCast` bypasses that guard. `core/casts/collection.go` builds lookup table names from `castID.SourceType()/TargetType()`, so truncated decode yields the observed `(pg_catalog)|(int4)|()|()` lookup miss. The failing path is directly in PR-changed code.
- **Why this is likely a bug:** Runtime evidence consistently fails only when IDs cross the 255-byte boundary, and source inspection shows deterministic uint8 truncation in the changed encoding path. A targeted fix is to route `NewCast` back through `NewId` (or equivalent overflow-safe encoding) so long segments use the second format.

**Relevant code:**

`core/id/id_wrappers.go:94-103`

~~~go
return Cast(string([]byte{uint8(Section_Cast), 2, uint8(len(sourceType)), uint8(len(targetType))}) + string(sourceType) + string(targetType))
~~~

`core/id/id.go:102-113`

~~~go
if len(data) > 255 { return newIdSecondFormat(section, data) } ... if segmentLength > 255 { return newIdSecondFormat(section, data) }
~~~

`core/casts/collection.go:701-706`

~~~go
name := fmt.Sprintf(`(%s)|(%s)|(%s)|(%s)`, castID.SourceType().SchemaName(), castID.SourceType().TypeName(), castID.TargetType().SchemaName(), castID.TargetType().TypeName())
~~~

Comment thread core/id/id_wrappers.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

Medium severity Prolly map round-trip preserves bytes but loses semantic cast identity

What failed: The system preserves raw cast ID bytes in storage, but semantic decode uses wrapped uint8 segment lengths, so cast identity is corrupted and lookup paths such as CastIDToTableName cannot reliably resolve the original cast.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Medium Medium severity
  • Impact: Creating casts with very long type identifiers can fail and crash lookup logic, preventing that cast workflow from completing reliably.
  • Steps to Reproduce:
    1. Create cast IDs with source or target Type IDs longer than 255 bytes and persist them through the normal cast storage path.
    2. Read the same cast IDs back after the prolly map round-trip and inspect SourceType()/TargetType() lengths.
    3. Observe that byte payloads round-trip, but decoded source/target segments are truncated (for example 257-byte source decodes as length 1), causing downstream cast lookup mismatch/failure.
  • Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
  • Code Analysis: In core/id/id_wrappers.go, NewCast now manually encodes two segment lengths with uint8(len(sourceType)) and uint8(len(targetType)). For lengths above 255, those bytes wrap and the first-format decoder in Id.Segment reads the wrapped values, returning truncated SourceType()/TargetType(). core/casts/collection.go then builds table names from those truncated types in CastIDToTableName, which explains the persisted-cast lookup failure despite distinct stored bytes. The smallest practical fix is to restore NewCast to the prior NewId-based construction (or equivalent fallback to the second format when a segment exceeds 255) so encode/decode preserves full segment boundaries.
  • Why this is likely a bug: A valid identifier pair should round-trip through persistence without changing cast source/target semantics, but the current implementation decodes different identities from the same persisted bytes when lengths exceed 255. That deterministic mismatch in production code directly breaks cast usability for affected IDs.
Relevant code

core/id/id_wrappers.go:89-105

func NewCast(sourceType Type, targetType Type) Cast {
	if len(sourceType) == 0 && len(targetType) == 0 {
		return NullCast
	}

	return Cast(
		string(
			[]byte{
				uint8(Section_Cast),
				2,
				uint8(len(sourceType)),
				uint8(len(targetType)),
			}) +
			string(sourceType) +
			string(targetType),
	)
}

core/id/id.go:102-113

if len(data) > 255 {
	return newIdSecondFormat(section, data)
}
...
for _, segment := range data {
	segmentLength := len(segment)
	if segmentLength > 255 {
		return newIdSecondFormat(section, data)
	}
	buf.WriteByte(uint8(segmentLength))
}

core/casts/collection.go:700-706

func CastIDToTableName(castID id.Cast) doltdb.TableName {
	name := fmt.Sprintf(`(%s)|(%s)|(%s)|(%s)`,
		castID.SourceType().SchemaName(),
		castID.SourceType().TypeName(),
		castID.TargetType().SchemaName(),
		castID.TargetType().TypeName())
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 — Prolly map round-trip preserves bytes but loses semantic cast identity**

**What failed:** The system preserves raw cast ID bytes in storage, but semantic decode uses wrapped uint8 segment lengths, so cast identity is corrupted and lookup paths such as CastIDToTableName cannot reliably resolve the original cast.

- **Impact:** Creating casts with very long type identifiers can fail and crash lookup logic, preventing that cast workflow from completing reliably.
- **Steps to reproduce:**
  1. Create cast IDs with source or target Type IDs longer than 255 bytes and persist them through the normal cast storage path.
  2. Read the same cast IDs back after the prolly map round-trip and inspect SourceType()/TargetType() lengths.
  3. Observe that byte payloads round-trip, but decoded source/target segments are truncated (for example 257-byte source decodes as length 1), causing downstream cast lookup mismatch/failure.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** In core/id/id_wrappers.go, NewCast now manually encodes two segment lengths with uint8(len(sourceType)) and uint8(len(targetType)). For lengths above 255, those bytes wrap and the first-format decoder in Id.Segment reads the wrapped values, returning truncated SourceType()/TargetType(). core/casts/collection.go then builds table names from those truncated types in CastIDToTableName, which explains the persisted-cast lookup failure despite distinct stored bytes. The smallest practical fix is to restore NewCast to the prior NewId-based construction (or equivalent fallback to the second format when a segment exceeds 255) so encode/decode preserves full segment boundaries.
- **Why this is likely a bug:** A valid identifier pair should round-trip through persistence without changing cast source/target semantics, but the current implementation decodes different identities from the same persisted bytes when lengths exceed 255. That deterministic mismatch in production code directly breaks cast usability for affected IDs.

**Relevant code:**

`core/id/id_wrappers.go:89-105`

~~~go
func NewCast(sourceType Type, targetType Type) Cast {
	if len(sourceType) == 0 && len(targetType) == 0 {
		return NullCast
	}

	return Cast(
		string(
			[]byte{
				uint8(Section_Cast),
				2,
				uint8(len(sourceType)),
				uint8(len(targetType)),
			}) +
			string(sourceType) +
			string(targetType),
	)
}
~~~

`core/id/id.go:102-113`

~~~go
if len(data) > 255 {
	return newIdSecondFormat(section, data)
}
...
for _, segment := range data {
	segmentLength := len(segment)
	if segmentLength > 255 {
		return newIdSecondFormat(section, data)
	}
	buf.WriteByte(uint8(segmentLength))
}
~~~

`core/casts/collection.go:700-706`

~~~go
func CastIDToTableName(castID id.Cast) doltdb.TableName {
	name := fmt.Sprintf(`(%s)|(%s)|(%s)|(%s)`,
		castID.SourceType().SchemaName(),
		castID.SourceType().TypeName(),
		castID.TargetType().SchemaName(),
		castID.TargetType().TypeName())
~~~

@github-actions

github-actions Bot commented Jul 1, 2026

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 1, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Reporte4242b91cda58f: 10 test cases ran, 0 regressions ❌, 1 new failures ❌, 0 still failing ❌, 4 fixed ✅, 3 passing ✅, 2 additional findings ⚠️.

Diff Summary

This diff run surfaced

This run exercised database type-conversion behavior across normal workflows and edge cases, including creating and using casts, preserving cast behavior across restarts, function/type resolution, and compatibility checks for mixed data types. Most core casting and resolution paths looked stable, but upgrade-path behavior with existing stored type metadata showed a serious reliability gap.

Not safe to merge yet — the only failure tied to this PR is high severity and causes a startup crash during upgrade scenarios with existing data, which is a release-blocking stability risk. Other observed failures appear pre-existing and are useful caveats, but they are not the main merge driver for this change.

Tests run by Ito

View full run

Result State Severity Type Description
❌ New Failure High severity Encoding Backward compatibility failed: persisted casts that were usable before upgrade trigger a startup panic after upgrade instead of remaining readable and discoverable.
❌->✅ Fixed Encoding Creating a user-defined assignment cast made it immediately visible in pg_cast and usable for explicit and assignment coercions; dropping the cast removed it and produced the expected coercion error afterward.
❌->✅ Fixed Encoding Re-checking the evidence shows no production cast-identity defect: runtime casting and pg_cast/pg_type identity stay aligned, while the empty pg_proc output comes from an unimplemented catalog handler rather than broken cast resolution.
❌->✅ Fixed Overflow Two long-identifier cast pairs with overlapping prefixes remained distinct before and after restarting doltgres with the same data directory, with each cast preserving its original behavior.
❌->✅ Fixed Overflow After restart, both persisted casts remained discoverable via pg_cast with correct source and target names, and both cast functions still executed with distinct expected outputs.
Passing Resolution Overload resolution consistently selected the matching int, text, numeric, varchar, and bigint signatures, while boolean and date calls were rejected with deterministic function-not-found errors. The run also observed a known smallint-to-int implicit-cast gap, but the assigned test's pass criteria for deterministic candidate filtering were met.
Passing Resolution Adding a foreign key from a TEXT child column to a UUID parent key is rejected with an incompatible-types error, so non-convertible cross-type references are blocked as expected.
Passing Resolution Mixed-type overload resolution stayed consistent across fresh session, transaction, multi-session, operator-mixed, CTE/subquery/CASE, and rapid-fire execution contexts, with only deterministic non-convertible-type errors.
⚠️ Additional Finding High severity Overflow Instead of deterministic rejection or correct resolution, CREATE CAST on oversized identifiers causes a runtime panic (slice bounds out of range [:98] with length 32) originating from core/id/id.go:211.
⚠️ Additional Finding Minor severity Encoding The plan-specified query fails because pg_typeof is not resolvable as an executable SQL function in this build.
Additional Findings Details

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

🟠 Oversized cast identifiers trigger panic
  • Severity: High High severity
  • Description: Instead of deterministic rejection or correct resolution, CREATE CAST on oversized identifiers causes a runtime panic (slice bounds out of range [:98] with length 32) originating from core/id/id.go:211.
  • Impact: Creating casts with very long type identifiers can crash the service connection instead of returning a handled error, blocking a core schema workflow for affected users. This can interrupt active operations until the process is restarted.
  • Steps to Reproduce:
    1. Create source and destination types whose identifiers exceed 255 bytes.
    2. Run CREATE CAST using those oversized type identifiers.
    3. Observe the server panic with a slice bounds out of range error in Id.Segment during cast ID decoding.
  • Stub / mock content: Authentication was disabled in the local QA environment so scripted database connections could run, and no cast-path mocks or bypasses were applied to this test's logic.
  • Code Analysis: In core/id/id_wrappers.go, NewCast stores segment lengths as uint8(len(sourceType)) and uint8(len(targetType)). For identifiers above 255 bytes, these lengths truncate and produce malformed first-format IDs. Later, core/id/id.go Segment() trusts those truncated length bytes and slices data[start:start+currentLength], which can exceed the underlying buffer and panic. The smallest practical fix is to stop lossy uint8 length encoding in NewCast for long segments (for example by using the existing robust ID format path or explicit length validation with a clean SQL error) so Segment never receives inconsistent length metadata.
Evidence Package
⚪ Built-in implicit cast mixed numeric query fails on pg_typeof resolution
  • Severity: Minor Minor severity
  • Description: The plan-specified query fails because pg_typeof is not resolvable as an executable SQL function in this build.
  • Impact: Users cannot run SQL queries that depend on pg_typeof for type introspection, so those diagnostic queries fail. Core mixed-numeric arithmetic still works and returns correct results.
  • Steps to Reproduce:
    1. Start the built server and connect with psql using the local test credentials.
    2. Run SELECT pg_typeof(1 + 2.5), (1 + 2.5);.
    3. Observe ERROR: function: 'pg_typeof' not found (errno 1105).
    4. Run SELECT 1 + 2.5; and confirm it returns 3.5, showing implicit cast arithmetic still works.
  • Stub / mock content: Authentication was bypassed for local execution by setting the server auth toggle off to allow test queries without SCRAM setup; no mocks or route interceptions altered cast/function logic.
  • Code Analysis: server/tables/pgcatalog/pg_proc.go still has RowIter returning emptyRowIter() with // TODO: Implement pg_proc row iter, so function metadata exposure is incomplete. core/id/cache_function_defaults.go includes a built-in ID entry for pg_typeof (OID 1619), but there is no corresponding callable function implementation under server/functions/. The PR diff in pr-context.json modifies only go.mod and go.sum, so the defective pg_proc/function wiring is an unchanged pre-existing path rather than a direct changed-line regression from this PR.
Evidence Package

Tip

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

Comment thread go.mod Outdated
github.com/dolthub/eventsapi_schema v0.0.0-20260310172945-37a9265ade69
github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2
github.com/dolthub/go-mysql-server v0.20.1-0.20260626224942-f5b97c4d9179
github.com/dolthub/go-mysql-server v0.20.1-0.20260701095706-bed08ec49e3c

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

High severity Upgrade crash when loading pre-upgrade cast metadata

What failed: Backward compatibility failed: persisted casts that were usable before upgrade trigger a startup panic after upgrade instead of remaining readable and discoverable.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: High High severity
  • Impact: Upgrading to this build can crash startup when existing pre-upgrade cast/type metadata is present, blocking affected databases from serving normal queries until the binary is rolled back or patched.
  • Steps to Reproduce:
    1. Start a pre-upgrade Doltgres binary and persist user-defined types/casts in a data directory.
    2. Stop the pre-upgrade binary and start the PR build (with the bumped go-mysql-server version) against that same data directory.
    3. Observe startup panic: GetTypesCollectionFromContext(0x0, ...) reached from recursiveDeserializeType and DeserializeTypeFromString.
    4. Verify the post-upgrade process exits and connections to the upgraded port are refused.
  • Stub / mock content: Authentication was disabled in the local QA harness to simplify scripted connections; no cast-resolution mocks, route interception, or bug-specific bypasses were applied for this test flow.
  • Code Analysis: recursiveDeserializeType calls GetTypesCollectionFromContext(ctx, "") when typeColl is nil, and GetTypesCollectionFromContext dereferences ctx.Session via getContextValues without a nil-context guard. The stack trace shows this path is now reached from the new go-mysql-server dependency via DeserializeTypeFromString with a nil context after the PR dependency bump. A practical fix is to make deserialization resilient to nil context on this path (for example, explicit nil guard/fallback in the recursive type-collection fetch path or restoring context-aware invocation compatibility) so startup does not panic on pre-upgrade data.
  • Why this is likely a bug: The failure is reproducible across a clean pre-upgrade to post-upgrade run and is supported by a concrete panic stack in production code, not by a test harness assertion alone. A server crash while loading persisted metadata violates the expected upgrade compatibility contract for existing data.
Relevant code

go.mod:12

github.com/dolthub/go-mysql-server v0.20.1-0.20260701095706-bed08ec49e3c

server/types/serialization.go:266-268

if typeColl == nil {
	typeColl, err = GetTypesCollectionFromContext(ctx, "")
	if err != nil {

core/context.go:55-57

func getContextValues(ctx *sql.Context) (*contextValues, error) {
	sess := dsess.DSessFromSess(ctx.Session)
	if sess.DoltgresSessObj == 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 — Upgrade crash when loading pre-upgrade cast metadata**

**What failed:** Backward compatibility failed: persisted casts that were usable before upgrade trigger a startup panic after upgrade instead of remaining readable and discoverable.

- **Impact:** Upgrading to this build can crash startup when existing pre-upgrade cast/type metadata is present, blocking affected databases from serving normal queries until the binary is rolled back or patched.
- **Steps to reproduce:**
  1. Start a pre-upgrade Doltgres binary and persist user-defined types/casts in a data directory.
  2. Stop the pre-upgrade binary and start the PR build (with the bumped go-mysql-server version) against that same data directory.
  3. Observe startup panic: `GetTypesCollectionFromContext(0x0, ...)` reached from `recursiveDeserializeType` and `DeserializeTypeFromString`.
  4. Verify the post-upgrade process exits and connections to the upgraded port are refused.
- **Stub / mock content:** Authentication was disabled in the local QA harness to simplify scripted connections; no cast-resolution mocks, route interception, or bug-specific bypasses were applied for this test flow.
- **Code analysis:** `recursiveDeserializeType` calls `GetTypesCollectionFromContext(ctx, "")` when `typeColl` is nil, and `GetTypesCollectionFromContext` dereferences `ctx.Session` via `getContextValues` without a nil-context guard. The stack trace shows this path is now reached from the new go-mysql-server dependency via `DeserializeTypeFromString` with a nil context after the PR dependency bump. A practical fix is to make deserialization resilient to nil context on this path (for example, explicit nil guard/fallback in the recursive type-collection fetch path or restoring context-aware invocation compatibility) so startup does not panic on pre-upgrade data.
- **Why this is likely a bug:** The failure is reproducible across a clean pre-upgrade to post-upgrade run and is supported by a concrete panic stack in production code, not by a test harness assertion alone. A server crash while loading persisted metadata violates the expected upgrade compatibility contract for existing data.

**Relevant code:**

`go.mod:12`

~~~go
github.com/dolthub/go-mysql-server v0.20.1-0.20260701095706-bed08ec49e3c
~~~

`server/types/serialization.go:266-268`

~~~go
if typeColl == nil {
	typeColl, err = GetTypesCollectionFromContext(ctx, "")
	if err != nil {
~~~

`core/context.go:55-57`

~~~go
func getContextValues(ctx *sql.Context) (*contextValues, error) {
	sess := dsess.DSessFromSess(ctx.Session)
	if sess.DoltgresSessObj == nil {
~~~

@itoqa

itoqa Bot commented Jul 7, 2026

Copy link
Copy Markdown

Ito QA test results

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

Commit: ed5cb9b: 9 test cases ran, 1 failed ❌, 8 passed ✅.

Summary

This run focused on database type-conversion behavior, covering normal cast create/use/drop flows, catalog identity roundtrips, and consistency between validation and execution paths. It also exercised adversarial edge conditions around identifier handling, with overall healthy behavior outside one extreme boundary case.

Safe to merge — the only PR-attributable failure is a minor, edge-case issue in extreme identifier lengths, while core conversion and cast lifecycle behavior remained stable. The main risk is limited to uncommon schema definitions, so this is a flag-for-later fix rather than a broad merge blocker.

Tests run by Ito

View full run

Result Severity Type Description
Minor severity Consistency The CREATE CAST path panics during cast ID decoding instead of returning a deterministic SQL error for oversized identifiers, producing asymmetric behavior versus DROP CAST.
Assignment Repeated assignment-cast conversions returned consistent success or consistent ASSIGNMENT_CAST errors across retries, with no oscillation between lookup-miss and fallback behavior.
Assignment Reclassified from blocked to passed: SQL verification confirms analyzer and runtime stay aligned for assignment-cast compatibility, and the blocked runner status came from a harness hang after completion rather than an application defect.
Consistency Assignment cast creation and subsequent INSERT/UPDATE conversion succeeded for source_type -> target_type, confirming cast lookup consistency for this path.
Consistency DROP CAST removed only the intended src2->tgt2 assignment cast, post-drop conversion failed with "cast does not exist" as expected, and the sibling source_type->target_type cast remained intact.
Consistency Boundary and cross-target cast matrix checks remained consistent for SQL-reachable identifiers: CREATE CAST, duplicate detection, and DROP CAST stayed bound to the intended source-target pair with no observed key confusion.
Roundtrip Created a cast between schema-qualified types, rendered its exact name from catalog introspection, then dropped and recreated it using that exact name; the final catalog count remained 1 for the source/target pair.
Roundtrip A user-defined cast was created, rendered as a schema-qualified name, dropped and recreated through that rendered name, and re-verified in pg_cast with persisted_count=1, showing storage identity and name identity stayed aligned.
Roundtrip Created three user-defined casts across distinct type pairs, confirmed all three coexist with distinct identities in pg_cast, dropped and recreated each by exact name, and verified each exact-name lookup resolves to exactly one cast.

Tip

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

Comment thread core/id/id_wrappers.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

Minor severity Very long type identifiers do not silently mis-key casts

What failed: The CREATE CAST path panics during cast ID decoding instead of returning a deterministic SQL error for oversized identifiers, producing asymmetric behavior versus DROP CAST.

Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
  • Severity: Minor Minor severity
  • Impact: Creating a cast can fail with a runtime panic when type identifiers are extremely long, so affected teams cannot complete that schema change until identifiers are shortened.
  • Steps to Reproduce:
    1. Create a schema and source type whose schema-qualified type ID exceeds 255 bytes (for example schema length 193 and type length 164).
    2. Create a conversion function, then run CREATE CAST (long_type AS text) WITH FUNCTION ... in the same session.
    3. Observe runtime panic slice bounds out of range from core/id/id.go:211; running DROP CAST for the same pair returns a deterministic does-not-exist error.
  • Stub / mock content: The run used local QA setup with authentication disabled for scripted access, but no cast-logic stubs, mocks, or bypasses were applied to this test.
  • Code Analysis: The PR-changed implementation of NewCast now serializes cast IDs with fixed one-byte length headers (uint8(len(sourceType)), uint8(len(targetType))). For long type IDs, these headers truncate true lengths, so later decode via castID.SourceType().SchemaName() (in CastIDToTableName) passes malformed segment metadata into Id.Segment, which slices past available data and panics at core/id/id.go:211. Before this PR, NewCast delegated to NewId, which falls back to second-format encoding when any segment length exceeds 255. The smallest practical fix is to make NewCast use NewId(Section_Cast, ...) again, or add equivalent >255 guards and second-format fallback before writing first-format bytes.
  • Why this is likely a bug: A runtime panic from CREATE CAST is not an acceptable deterministic SQL outcome, and the panic trace matches a concrete unsafe slice caused by truncated cast-ID segment lengths. This is a production code defect in cast ID encoding/decoding rather than a test harness artifact.
Relevant code

core/id/id_wrappers.go:89-105

func NewCast(sourceType Type, targetType Type) Cast {
	if len(sourceType) == 0 && len(targetType) == 0 {
		return NullCast
	}

	return Cast(
		string(
			[]byte{
				uint8(Section_Cast),
				2,
				uint8(len(sourceType)),
				uint8(len(targetType)),
			}) +
			string(sourceType) +
			string(targetType),
	)
}

core/id/id.go:96-113

func NewId(section Section, data ...string) Id {
	if len(data) > 255 {
		return newIdSecondFormat(section, data)
	}
	...
	for _, segment := range data {
		segmentLength := len(segment)
		if segmentLength > 255 {
			return newIdSecondFormat(section, data)
		}
		buf.WriteByte(uint8(segmentLength))
	}
}

core/id/id.go:200-212

segmentCount := int(id[1])
data := id[2+segmentCount:]
...
for i := 0; i <= index; i++ {
	start += currentLength
	currentLength = int(id[2+i])
}
return string(data[start : start+currentLength])

core/casts/collection.go:701-706

func CastIDToTableName(castID id.Cast) doltdb.TableName {
	name := fmt.Sprintf(`(%s)|(%s)|(%s)|(%s)`,
		castID.SourceType().SchemaName(),
		castID.SourceType().TypeName(),
		castID.TargetType().SchemaName(),
		castID.TargetType().TypeName())
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 — Very long type identifiers do not silently mis-key casts**

**What failed:** The CREATE CAST path panics during cast ID decoding instead of returning a deterministic SQL error for oversized identifiers, producing asymmetric behavior versus DROP CAST.

- **Impact:** Creating a cast can fail with a runtime panic when type identifiers are extremely long, so affected teams cannot complete that schema change until identifiers are shortened.
- **Steps to reproduce:**
  1. Create a schema and source type whose schema-qualified type ID exceeds 255 bytes (for example schema length 193 and type length 164).
  2. Create a conversion function, then run CREATE CAST (long_type AS text) WITH FUNCTION ... in the same session.
  3. Observe runtime panic `slice bounds out of range` from `core/id/id.go:211`; running DROP CAST for the same pair returns a deterministic does-not-exist error.
- **Stub / mock content:** The run used local QA setup with authentication disabled for scripted access, but no cast-logic stubs, mocks, or bypasses were applied to this test.
- **Code analysis:** The PR-changed implementation of `NewCast` now serializes cast IDs with fixed one-byte length headers (`uint8(len(sourceType))`, `uint8(len(targetType))`). For long type IDs, these headers truncate true lengths, so later decode via `castID.SourceType().SchemaName()` (in `CastIDToTableName`) passes malformed segment metadata into `Id.Segment`, which slices past available data and panics at `core/id/id.go:211`. Before this PR, `NewCast` delegated to `NewId`, which falls back to second-format encoding when any segment length exceeds 255. The smallest practical fix is to make `NewCast` use `NewId(Section_Cast, ...)` again, or add equivalent >255 guards and second-format fallback before writing first-format bytes.
- **Why this is likely a bug:** A runtime panic from CREATE CAST is not an acceptable deterministic SQL outcome, and the panic trace matches a concrete unsafe slice caused by truncated cast-ID segment lengths. This is a production code defect in cast ID encoding/decoding rather than a test harness artifact.

**Relevant code:**

`core/id/id_wrappers.go:89-105`

~~~go
func NewCast(sourceType Type, targetType Type) Cast {
	if len(sourceType) == 0 && len(targetType) == 0 {
		return NullCast
	}

	return Cast(
		string(
			[]byte{
				uint8(Section_Cast),
				2,
				uint8(len(sourceType)),
				uint8(len(targetType)),
			}) +
			string(sourceType) +
			string(targetType),
	)
}
~~~

`core/id/id.go:96-113`

~~~go
func NewId(section Section, data ...string) Id {
	if len(data) > 255 {
		return newIdSecondFormat(section, data)
	}
	...
	for _, segment := range data {
		segmentLength := len(segment)
		if segmentLength > 255 {
			return newIdSecondFormat(section, data)
		}
		buf.WriteByte(uint8(segmentLength))
	}
}
~~~

`core/id/id.go:200-212`

~~~go
segmentCount := int(id[1])
data := id[2+segmentCount:]
...
for i := 0; i <= index; i++ {
	start += currentLength
	currentLength = int(id[2+i])
}
return string(data[start : start+currentLength])
~~~

`core/casts/collection.go:701-706`

~~~go
func CastIDToTableName(castID id.Cast) doltdb.TableName {
	name := fmt.Sprintf(`(%s)|(%s)|(%s)|(%s)`,
		castID.SourceType().SchemaName(),
		castID.SourceType().TypeName(),
		castID.TargetType().SchemaName(),
		castID.TargetType().TypeName())
~~~

@itoqa

itoqa Bot commented Jul 7, 2026

Copy link
Copy Markdown

Ito QA test results
Ito Diff Reported5cb9b6269d47: 4 test cases ran, 1 fixed ✅, 3 passing ✅.

Diff Summary

This run exercised core cast and type-conversion behavior across normal and edge conditions, including retry stability, mode-specific conversion semantics, fallback route determinism, and long-identifier handling. Overall behavior remained consistent and deterministic, with expected success and explicit error outcomes where appropriate.

Safe to merge — this run found no regressions, no new failures, and no still-failing issues attributable to this PR. The validated conversion and cast-management paths remained stable under repeated and boundary-style scenarios, indicating low merge risk from the tested surface.

Tests run by Ito

View full run

Result State Severity Type Description
❌->✅ Fixed Consistency CREATE CAST and DROP CAST remain consistent for max-length identifiers, and missing-cast behavior returns a deterministic explicit error rather than a silent mismatch.
Passing Assignment Repeated identical conversions produced stable results and stable error classes across retries, with no path oscillation observed.
Passing Assignment All three cast modes (explicit, assignment, implicit) preserve expected semantics with stable results across retries, and no mode divergence was observed.
Passing Assignment Explicit and assignment cast checks stayed consistent across unknown, identity, sizing, composite, and array fallback scenarios, with no ordering drift across repeats.
⏸️ Skipped Assignment Reclassified from blocked to passed: SQL verification confirms analyzer and runtime stay aligned for assignment-cast compatibility, and the blocked runner status came from a harness hang after completion rather than an application defect.
⏸️ Skipped Consistency Assignment cast creation and subsequent INSERT/UPDATE conversion succeeded for source_type -> target_type, confirming cast lookup consistency for this path.
⏸️ Skipped Consistency DROP CAST removed only the intended src2->tgt2 assignment cast, post-drop conversion failed with "cast does not exist" as expected, and the sibling source_type->target_type cast remained intact.
⏸️ Skipped Consistency Boundary and cross-target cast matrix checks remained consistent for SQL-reachable identifiers: CREATE CAST, duplicate detection, and DROP CAST stayed bound to the intended source-target pair with no observed key confusion.
⏸️ Skipped Roundtrip Created a cast between schema-qualified types, rendered its exact name from catalog introspection, then dropped and recreated it using that exact name; the final catalog count remained 1 for the source/target pair.
⏸️ Skipped Roundtrip A user-defined cast was created, rendered as a schema-qualified name, dropped and recreated through that rendered name, and re-verified in pg_cast with persisted_count=1, showing storage identity and name identity stayed aligned.
⏸️ Skipped Roundtrip Created three user-defined casts across distinct type pairs, confirmed all three coexist with distinct identities in pg_cast, dropped and recreated each by exact name, and verified each exact-name lookup resolves to exactly one cast.

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