sqlString improvements#2781
Conversation
|
Ito Test Report ✅7 test cases ran. 1 additional finding, 6 passed. The unified run passed 6 of 7 TIME-related tests, confirming stable behavior for canonical formatting (including trailing-zero trimming), consistency between pgwire and ::text output paths, exact preservation of 24:00:00, default six-digit fractional precision when typmod is unspecified, and parse-stable high-fanout validation across 14,336 rows with no malformed values. ✅ Passed (6)ℹ️ Additional Findings (1)
🟠 TIME typmod precision casts do not enforce rounding
Relevant code:
typmod := val3.(int32)
if typmod == -1 {
typmod = 6
}
precision := tree.TimeFamilyPrecisionToRoundDuration(typmod)
if strings.EqualFold(input, "now") {
// timetypmodin represents the PostgreSQL function of time type IO typmod input.
var timetypmodin = framework.Function1{
Name: "timetypmodin",
Return: pgtypes.Int32,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.CstringArray},
Strict: true,
Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
// TODO: typmod=(precision<<16)∣scale
return nil, nil
},
}
// timetypmodout represents the PostgreSQL function of time type IO typmod output.
var timetypmodout = framework.Function1{
Name: "timetypmodout",
Return: pgtypes.Cstring,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.Int32},
Strict: true,
Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
// TODO
// Precision = typmod & 0xFFFF
// Scale = (typmod >> 16) & 0xFFFF
return nil, nil
},
}Commit: Tell us how we did: Give Ito Feedback |
|
Ito Diff Report ❌Tested: Overall, this unified run was mixed: 17 of 28 test cases passed, with broad success on canonical TIME formatting (fractional-zero trimming, pgwire/AST consistency, 24:00:00 preservation, column isolation, and loop stability), numeric/integer/mixed serializer stability, invalid TIME literal safety, and most VARCHAR multibyte/rune-boundary behaviors. The most important defects were 11 failures centered on three real issues: a critical PR-introduced startup crash in Function2 overload registration from nil parameter dereferences (blocking SQL service and related safety/buffer scenarios), a medium-severity TIME typmod precision bug where casts/round-trips and mixed-precision columns incorrectly fall back to six fractional digits, and a medium-severity VARCHAR cast bug where explicit varchar(n) casting can return untruncated over-limit values despite typmod constraints. ❌ Failures (7)🚨 Regtype output migration startup panic
Relevant code:
func keyForParamTypes(types []*pgtypes.DoltgresType) string {
sb := strings.Builder{}
for i, typ := range types {
if i > 0 {
sb.WriteByte(',')
}
sb.WriteString(typ.String())
}
return sb.String()
}
var int2out = framework.Function2{
Name: "int2out",
Return: pgtypes.Cstring,
Parameters: [2]*pgtypes.DoltgresType{nil, pgtypes.Int16},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val, dest any) (any, error) {
return strconv.AppendInt(dest.([]byte), int64(val.(int16)), 10), nil
},
}
var regtypeout = framework.Function2{
Name: "regtypeout",
Return: pgtypes.Cstring,
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Regtype},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val, dest any) (any, error) {
internalID := val.(id.Id)
if internalID.Section() == id.Section_OID {
return internalID.Segment(0), nil
}
toid := id.Cache().ToOID(internalID)🚨 Parallel encoding blocked by startup panic
Relevant code:
func keyForParamTypes(types []*pgtypes.DoltgresType) string {
sb := strings.Builder{}
for i, typ := range types {
if i > 0 {
sb.WriteByte(',')
}
sb.WriteString(typ.String())
}
return sb.String()
}
var time_out = framework.Function2{
Name: "time_out",
Return: pgtypes.Cstring,
Parameters: [2]*pgtypes.DoltgresType{nil, pgtypes.Time},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val, dest any) (any, error) {
return val.(timeofday.TimeOfDay).AppendBytes(dest.([]byte)), nil
},
}
var int2out = framework.Function2{
Name: "int2out",
Return: pgtypes.Cstring,
Parameters: [2]*pgtypes.DoltgresType{nil, pgtypes.Int16},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val, dest any) (any, error) {
return strconv.AppendInt(dest.([]byte), int64(val.(int16)), 10), nil
},
}🚨 Mixed type serialization contract regression
Relevant code:
// IoOutputBytes appends the given type value to dest
func (t *DoltgresType) IoOutputBytes(ctx *sql.Context, val any, dest []byte) ([]byte, error) {
var o any
var err error
if t.ModInFunc != 0 || t.IsArrayType() || t.IsCompositeType() {
send := globalFunctionRegistry.GetFunction(ctx, t.OutputFunc)
resolvedTypes := send.ResolvedTypes()
resolvedTypes[0] = t
o, err = send.WithResolvedTypes(resolvedTypes).(QuickFunction).CallVariadic(ctx, val, dest)
if err != nil {
return nil, err
}
return o.([]byte), err
}
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val, dest any) (any, error) {
internalID := val.(id.Id)
if internalID.Section() == id.Section_OID {
return internalID.Segment(0), nil
}
toid := id.Cache().ToOID(internalID)
if t, ok := types.OidToType[oid.Oid(toid)]; ok {
return t.SQLStandardName(), nil
} else {
return internalID.Segment(1), nil
}🚨 Core SQL startup panic before queries
Relevant code:
func keyForParamTypes(types []*pgtypes.DoltgresType) string {
sb := strings.Builder{}
for i, typ := range types {
if i > 0 {
sb.WriteByte(',')
}
sb.WriteString(typ.String())
}
var int4out = framework.Function2{
Name: "int4out",
Return: pgtypes.Cstring,
Parameters: [2]*pgtypes.DoltgresType{nil, pgtypes.Int32},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val, dest any) (any, error) {
return strconv.AppendInt(dest.([]byte), int64(val.(int32)), 10), nil
},🚨 Sustained query scenario blocked by crash
Relevant code:
func compileNonOperatorFunction(funcName string, overloads []FunctionInterface) {
overloadTree := NewOverloads()
for _, functionOverload := range overloads {
if err := overloadTree.Add(functionOverload); err != nil {
panic(err)
}
}
func keyForParamTypes(types []*pgtypes.DoltgresType) string {
sb := strings.Builder{}
for i, typ := range types {
if i > 0 {
sb.WriteByte(',')
}
sb.WriteString(typ.String())
}🚨 Startup crash in overload registration
Relevant code:
func keyForParamTypes(types []*pgtypes.DoltgresType) string {
sb := strings.Builder{}
for i, typ := range types {
if i > 0 {
sb.WriteByte(',')
}
sb.WriteString(typ.String())
}
var time_out = framework.Function2{
Name: "time_out",
Return: pgtypes.Cstring,
Parameters: [2]*pgtypes.DoltgresType{nil, pgtypes.Time},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val, dest any) (any, error) {
return val.(timeofday.TimeOfDay).AppendBytes(dest.([]byte)), nil
},🟠 Typmod precision not applied on time round trip
Relevant code:
case "oid", "regclass", "regproc", "regtype":
if _, ok := v.(id.Id); ok {
return v, sql.InRange, nil
}
case "time":
if _, ok := v.(timeofday.TimeOfDay); ok {
return v, sql.InRange, nil
}
typmod := val3.(int32)
if typmod == -1 {
typmod = 6
}
precision := tree.TimeFamilyPrecisionToRoundDuration(typmod)
if strings.EqualFold(input, "now") {
t := ctx.QueryTime()
t = t.Round(precision)
return timeofday.New(t.Hour(), t.Minute(), t.Second(), t.Nanosecond()/1000), nil
}
t, _, err := tree.ParseDTime(nil, input, precision)
if err != nil {
return nil, err
}
return timeofday.TimeOfDay(*t), nil
t, dependsOnContext, err := pgdate.ParseTimeWithoutTimezone(now, pgdate.ParseModeYMD, s)
if err != nil {
// Build our own error message to avoid exposing the dummy date.
return nil, false, makeParseError(s, types.Time, nil)
}
return MakeDTime(timeofday.FromTime(t).Round(precision)), dependsOnContext, nil✅ Verified Passing (17)
|
| Category | Summary | Screenshot |
|---|---|---|
| Safety | 🚨 Repeated fractional TIME output verification is blocked by startup nil dereference. | ![]() |
🚨 Fractional TIME loop blocked at startup
- What failed: The repeated fractional TIME check cannot execute because startup panics first on nil parameter dereference in overload key generation.
- Impact: Users cannot run even basic TIME text queries because the server fails before accepting stable query traffic. This blocks both regression validation and normal database use.
- Steps to reproduce:
- Connect to Doltgres on localhost:5432.
- Execute repeated query loops selecting a fractional TIME value such as SELECT TIME '16:17:18.123400'::text.
- Observe startup panic in overload initialization before repeated result comparisons can proceed.
- Stub / mock context: The retry flow kept local authentication bypassed and included a temporary nil-parameter guard during environment troubleshooting. Even with that setup, the underlying startup and output-path defects remained reproducible and blocked the intended TIME verification loop.
- Code analysis: I inspected the migrated
time_outsignature and overload key generation path. The first parameter is intentionallynilin Function2 declarations, but overload key generation still assumes every pointer is non-nil. - Why this is likely a bug: The failure is a deterministic startup panic in production code, so it is a true application defect rather than a flaky test condition.
Relevant code:
server/functions/time.go (lines 68-75)
var time_out = framework.Function2{
Name: "time_out",
Return: pgtypes.Cstring,
Parameters: [2]*pgtypes.DoltgresType{nil, pgtypes.Time},
Strict: true,
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val, dest any) (any, error) {
return val.(timeofday.TimeOfDay).AppendBytes(dest.([]byte)), nil
},server/functions/framework/overloads.go (lines 63-70)
func keyForParamTypes(types []*pgtypes.DoltgresType) string {
sb := strings.Builder{}
for i, typ := range types {
if i > 0 {
sb.WriteByte(',')
}
sb.WriteString(typ.String())
}Tell us how we did: Give Ito Feedback
|
Apparently these functions HAVE to be defined the way they are. A possible solution would be duplicating the output functions specifically for outputting to wire format. However, that is difficult to maintain so these will be as is until we can come up with a better solution. |

























TODO: consider switching these to
Function1N?