Skip to content

Conversation

jycor
Copy link
Contributor

@jycor jycor commented Sep 22, 2025

changes:

  • reorganize more tests
  • fix partition test to truncate with warning
  • fix JSON test to match MySQL's message
  • partially fixes index comparison with type conversion
  • refactor comparison logic for IN operator
  • decimal truncation
  • add warning for negative unsigned cast

fixes:

partially addresses: dolthub/dolt#9739

Base automatically changed from james/truncation1 to main September 22, 2025 20:17
@jycor jycor changed the title more truncation refactoring truncation refactoring and partial decimal truncation implementation Sep 23, 2025
Copy link
Member

@zachmu zachmu left a comment

Choose a reason for hiding this comment

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

Overall this looks pretty good, new interfaces and methods are fine.

My main comment is that you appear to have changed the error handling semantics in calls to type.Convert() at many call sites, and this is probably pretty bad. I'm guessing this doesn't show up in practice because the current implementations don't produce errors in almost any case, but there's no guarantee that remains the case (esp with Doltgres types and future extensions). Nothing for it but to go through each call site and make sure you're keeping the same semantics for the non-truncation error cases.

Expected: []sql.Row{
{1, "ab"},
{2, "4"},
{1.0, "ab"},
Copy link
Member

Choose a reason for hiding this comment

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

I would call this a regression

mysql> create table types1 as select * from (values row(1+1,2+2), row(floor(1.5),concat("a","b"))) a (c,d) order by 1;
Query OK, 2 rows affected (0.03 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> show create table types1;
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table  | Create Table                                                                                                                                                |
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------+
| types1 | CREATE TABLE `types1` (
  `c` bigint NOT NULL DEFAULT '0',
  `d` varchar(3) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

sql/core.go Outdated

// TruncateStringToInt trims any whitespace from s, then truncates the string to the left most characters that make
// up a valid integer. Empty strings are converted "0". Additionally, returns a flag indicating if truncation occurred.
func TruncateStringToInt(s string) (string, bool) {
Copy link
Member

Choose a reason for hiding this comment

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

These probably don't belong in core.go. Maybe type.go would be better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree, but we unfortunately use these functions in sql.ConvertToBool and sql.EvaluateCondition, which are referenced in many, many places. sql can't import types because import cycles.

Copy link
Member

Choose a reason for hiding this comment

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

type.go in the sql package

sql/core.go Outdated

// TruncateStringToDouble trims any whitespace from s, then truncates the string to the left most characters that make
// up a valid double. Empty strings are converted "0". Additionally, returns a flag indicating if truncation occurred.
func TruncateStringToDouble(s string) (string, bool) {
Copy link
Member

Choose a reason for hiding this comment

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

Probably good to whip up a few basic unit tests for these, they're easy to screw up.

{"Input value is valid float32", types.Float32, sql.NewRow(float32(6)), float64(1.791759469228055), nil},
{"Input value is valid int64", types.Int64, sql.NewRow(int64(8)), float64(2.0794415416798357), nil},
{"Input value is valid int32", types.Int32, sql.NewRow(int32(10)), float64(2.302585092994046), nil},
{"Input value is valid string", types.Float64, sql.NewRow("2"), 0.6931471805599453, nil},
Copy link
Member

Choose a reason for hiding this comment

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

These tests are fine but I would write some targeted to the new parsing functions you added directly

seed = e.(int64)
}
e, _, err = types.Int64.Convert(ctx, e)
if err != nil && sql.ErrTruncatedIncorrect.Is(err) {
Copy link
Member

Choose a reason for hiding this comment

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

Same issue about error dropping

Copy link
Member

Choose a reason for hiding this comment

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

This applies in many other places, I'll stop mentioning it. Here the issue is that if conversion fails for any reason other than truncation, e will not necessarily be an int64 (note the err == nil handling in the original).

In other places, it seems like you're making an assumption about the error handling semantics of Convert which might be true now but are certainly not guaranteed by the interface and may not hold in the future. Gotta handle errors correctly everywhere they might occur.

}
}

leftLit := NewLiteral(originalLeft, in.Left().Type())
Copy link
Member

Choose a reason for hiding this comment

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

This seems pointless, only used once and ever reassigned

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's used in a for loop to determine comparison types between each of the elements in the right tuple.

However, the logic should match HashInTuple, so I refactored them both here: #3237

}

func convertToInt64(t NumberTypeImpl_, v interface{}) (int64, sql.ConvertInRange, error) {
func convertToInt64(t NumberTypeImpl_, v interface{}, round bool) (int64, sql.ConvertInRange, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Would be better to define a RoundingBehavior type alias here, always irritating to have random boolean flags at the end of a method signature like this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What does this mean?

inRange sql.ConvertInRange
}{
// Boolean, Int8, Uint8, ... all use convertToInt64
{
Copy link
Member

Choose a reason for hiding this comment

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

These could use some more variety, more edge cases (leading decimal points, multiple decimal points, mix in other symbols, etc)

Copy link
Member

@zachmu zachmu left a comment

Choose a reason for hiding this comment

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

LGTM

sql/core.go Outdated
signIndex := 0

for i := 0; i < len(s); i++ {
var i int
Copy link
Member

Choose a reason for hiding this comment

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

This function and the constants above belong in type.go, in the same package.

Copy link
Member

Choose a reason for hiding this comment

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

Also give it a method doc while you're in there.

@jycor jycor merged commit fb75d24 into main Sep 29, 2025
7 of 8 checks passed
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.

2 participants