Skip to content

dolthub/dolt#9530 - Fix auto-increment overflow handling #3103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 6 additions & 36 deletions enginetest/queries/script_queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -10481,12 +10481,10 @@ where
},
Assertions: []ScriptTestAssertion{
{
Skip: true,
Query: "insert into tinyint_tbl values (999)",
ExpectedErr: sql.ErrValueOutOfRange,
},
{
Skip: true,
Query: "insert into tinyint_tbl values (127)",
Expected: []sql.Row{
{types.OkResult{
Expand All @@ -10496,7 +10494,6 @@ where
},
},
{
Skip: true,
Query: "show create table tinyint_tbl;",
Expected: []sql.Row{
{"tinyint_tbl", "CREATE TABLE `tinyint_tbl` (\n" +
Expand All @@ -10507,12 +10504,10 @@ where
},

{
Skip: true,
Query: "insert into smallint_tbl values (99999);",
ExpectedErr: sql.ErrValueOutOfRange,
},
{
Skip: true,
Query: "insert into smallint_tbl values (32767);",
Expected: []sql.Row{
{types.OkResult{
Expand All @@ -10522,23 +10517,20 @@ where
},
},
{
Skip: true,
Query: "show create table smallint_tbl;",
Expected: []sql.Row{
{"smallint_tbl", "CREATE TABLE `smallint_tbl` (\n" +
" `i` smallint NOT NULL AUTO_INCREMENT,\n" +
" PRIMARY KEY (`i`)\n" +
") ENGINE=InnoDB AUTO_INCREMENT=36727 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"},
") ENGINE=InnoDB AUTO_INCREMENT=32767 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"},
},
},

{
Skip: true,
Query: "insert into mediumint_tbl values (99999999);",
ExpectedErr: sql.ErrValueOutOfRange,
},
{
Skip: true,
Query: "insert into mediumint_tbl values (8388607);",
Expected: []sql.Row{
{types.OkResult{
Expand All @@ -10548,7 +10540,6 @@ where
},
},
{
Skip: true,
Query: "show create table mediumint_tbl;",
Expected: []sql.Row{
{"mediumint_tbl", "CREATE TABLE `mediumint_tbl` (\n" +
Expand All @@ -10559,12 +10550,10 @@ where
},

{
Skip: true,
Query: "insert into int_tbl values (99999999999)",
ExpectedErr: sql.ErrValueOutOfRange,
},
{
Skip: true,
Query: "insert into int_tbl values (2147483647)",
Expected: []sql.Row{
{types.OkResult{
Expand All @@ -10574,7 +10563,6 @@ where
},
},
{
Skip: true,
Query: "show create table int_tbl;",
Expected: []sql.Row{
{"int_tbl", "CREATE TABLE `int_tbl` (\n" +
Expand All @@ -10585,12 +10573,10 @@ where
},

{
Skip: true,
Query: "insert into bigint_tbl values (99999999999999999999);",
ExpectedErr: sql.ErrValueOutOfRange,
},
{
Skip: true,
Query: "insert into bigint_tbl values (9223372036854775807);",
Expected: []sql.Row{
{types.OkResult{
Expand All @@ -10600,7 +10586,6 @@ where
},
},
{
Skip: true,
Query: "show create table bigint_tbl;",
Expected: []sql.Row{
{"bigint_tbl", "CREATE TABLE `bigint_tbl` (\n" +
Expand All @@ -10624,12 +10609,10 @@ where
},
Assertions: []ScriptTestAssertion{
{
Skip: true,
Query: "insert into tinyint_tbl values (999)",
ExpectedErr: sql.ErrValueOutOfRange,
},
{
Skip: true,
Query: "insert into tinyint_tbl values (255)",
Expected: []sql.Row{
{types.OkResult{
Expand All @@ -10639,23 +10622,20 @@ where
},
},
{
Skip: true,
Query: "show create table tinyint_tbl;",
Expected: []sql.Row{
{"tinyint_tbl", "CREATE TABLE `tinyint_tbl` (\n" +
" `i` tinyint NOT NULL AUTO_INCREMENT,\n" +
" `i` tinyint unsigned NOT NULL AUTO_INCREMENT,\n" +
" PRIMARY KEY (`i`)\n" +
") ENGINE=InnoDB AUTO_INCREMENT=255 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"},
},
},

{
Skip: true,
Query: "insert into smallint_tbl values (99999);",
ExpectedErr: sql.ErrValueOutOfRange,
},
{
Skip: true,
Query: "insert into smallint_tbl values (65535);",
Expected: []sql.Row{
{types.OkResult{
Expand All @@ -10665,23 +10645,20 @@ where
},
},
{
Skip: true,
Query: "show create table smallint_tbl;",
Expected: []sql.Row{
{"smallint_tbl", "CREATE TABLE `smallint_tbl` (\n" +
" `i` smallint NOT NULL AUTO_INCREMENT,\n" +
" `i` smallint unsigned NOT NULL AUTO_INCREMENT,\n" +
" PRIMARY KEY (`i`)\n" +
") ENGINE=InnoDB AUTO_INCREMENT=65535 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"},
},
},

{
Skip: true,
Query: "insert into mediumint_tbl values (999999999);",
ExpectedErr: sql.ErrValueOutOfRange,
},
{
Skip: true,
Query: "insert into mediumint_tbl values (16777215);",
Expected: []sql.Row{
{types.OkResult{
Expand All @@ -10691,23 +10668,20 @@ where
},
},
{
Skip: true,
Query: "show create table mediumint_tbl;",
Expected: []sql.Row{
{"mediumint_tbl", "CREATE TABLE `mediumint_tbl` (\n" +
" `i` mediumint NOT NULL AUTO_INCREMENT,\n" +
" `i` mediumint unsigned NOT NULL AUTO_INCREMENT,\n" +
" PRIMARY KEY (`i`)\n" +
") ENGINE=InnoDB AUTO_INCREMENT=16777215 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"},
},
},

{
Skip: true,
Query: "insert into int_tbl values (99999999999)",
ExpectedErr: sql.ErrValueOutOfRange,
},
{
Skip: true,
Query: "insert into int_tbl values (4294967295)",
Expected: []sql.Row{
{types.OkResult{
Expand All @@ -10717,23 +10691,20 @@ where
},
},
{
Skip: true,
Query: "show create table int_tbl;",
Expected: []sql.Row{
{"int_tbl", "CREATE TABLE `int_tbl` (\n" +
" `i` int NOT NULL AUTO_INCREMENT,\n" +
" `i` int unsigned NOT NULL AUTO_INCREMENT,\n" +
" PRIMARY KEY (`i`)\n" +
") ENGINE=InnoDB AUTO_INCREMENT=4294967295 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"},
},
},

{
Skip: true,
Query: "insert into bigint_tbl values (999999999999999999999);",
ExpectedErr: sql.ErrValueOutOfRange,
},
{
Skip: true,
Query: "insert into bigint_tbl values (18446744073709551615);",
Expected: []sql.Row{
{types.OkResult{
Expand All @@ -10743,11 +10714,10 @@ where
},
},
{
Skip: true,
Query: "show create table bigint_tbl;",
Expected: []sql.Row{
{"bigint_tbl", "CREATE TABLE `bigint_tbl` (\n" +
" `i` bigint NOT NULL AUTO_INCREMENT,\n" +
" `i` bigint unsigned NOT NULL AUTO_INCREMENT,\n" +
" PRIMARY KEY (`i`)\n" +
") ENGINE=InnoDB AUTO_INCREMENT=18446744073709551615 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"},
},
Expand Down
45 changes: 43 additions & 2 deletions memory/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"encoding/gob"
"fmt"
"io"
"math"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -1144,9 +1145,32 @@ func (t *Table) Insert(ctx *sql.Context, row sql.Row) error {
func (t *Table) PeekNextAutoIncrementValue(ctx *sql.Context) (uint64, error) {
data := t.sessionTableData(ctx)

// Find the auto increment column to validate the current value
autoCol := t.getAutoIncrementColumn()
if autoCol == nil {
return data.autoIncVal, nil
}

// If the current auto increment value is out of range for the column type,
// return the maximum valid value instead
if _, inRange, err := autoCol.Type.Convert(ctx, data.autoIncVal); err == nil && inRange == sql.OutOfRange {
return data.autoIncVal - 1, nil
}

return data.autoIncVal, nil
}

// getAutoIncrementColumn returns the auto increment column for this table, or nil if none exists.
// Only one auto increment column is allowed per table.
func (t *Table) getAutoIncrementColumn() *sql.Column {
for _, col := range t.Schema() {
if col.AutoIncrement {
return col
}
}
return nil
}

// GetNextAutoIncrementValue gets the next auto increment value for the memory table the increment.
func (t *Table) GetNextAutoIncrementValue(ctx *sql.Context, insertVal interface{}) (uint64, error) {
data := t.sessionTableData(ctx)
Expand All @@ -1163,7 +1187,6 @@ func (t *Table) GetNextAutoIncrementValue(ctx *sql.Context, insertVal interface{
}
data.autoIncVal = v.(uint64)
}

return data.autoIncVal, nil
}

Expand Down Expand Up @@ -1257,7 +1280,7 @@ func addColumnToSchema(ctx *sql.Context, data *TableData, newCol *sql.Column, or
data.autoIncVal = 0
}

data.autoIncVal++
updateAutoIncrementSafe(ctx, newCol, &data.autoIncVal)
}

newPkOrds := data.schema.PkOrdinals
Expand Down Expand Up @@ -2551,3 +2574,21 @@ func (t *TableRevision) AddColumn(ctx *sql.Context, column *sql.Column, order *s
func (t *TableRevision) IgnoreSessionData() bool {
return true
}

// updateAutoIncrementSafe safely increments an auto_increment value, handling overflow
// by ensuring it doesn't exceed the column type's maximum value or wrap around.
func updateAutoIncrementSafe(ctx *sql.Context, autoCol *sql.Column, autoIncVal *uint64) {
currentVal := *autoIncVal

// Check for arithmetic overflow before adding 1
if currentVal == math.MaxUint64 {
// At maximum uint64 value, can't increment further
return
}

nextVal := currentVal + 1
if _, inRange, err := autoCol.Type.Convert(ctx, nextVal); err == nil && inRange == sql.InRange {
*autoIncVal = nextVal
}
// If next value would be out of range for the column type, stay at current value
}
10 changes: 4 additions & 6 deletions memory/table_editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,16 +188,14 @@ func (t *tableEditor) Insert(ctx *sql.Context, row sql.Row) error {
return err
}
if cmp > 0 {
// Provided value larger than autoIncVal, set autoIncVal to that value
v, _, err := types.Uint64.Convert(ctx, row[idx])
insertedVal, _, err := types.Uint64.Convert(ctx, row[idx])
if err != nil {
return err
}
t.ea.TableData().autoIncVal = v.(uint64)
t.ea.TableData().autoIncVal++ // Move onto next autoIncVal
t.ea.TableData().autoIncVal = insertedVal.(uint64)
updateAutoIncrementSafe(ctx, autoCol, &t.ea.TableData().autoIncVal)
} else if cmp == 0 {
// Provided value equal to autoIncVal
t.ea.TableData().autoIncVal++ // Move onto next autoIncVal
updateAutoIncrementSafe(ctx, autoCol, &t.ea.TableData().autoIncVal)
}
}

Expand Down
5 changes: 4 additions & 1 deletion sql/expression/auto_increment.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ func (i *AutoIncrement) Eval(ctx *sql.Context, row sql.Row) (interface{}, error)
given = seq
}

ret, _, err := i.Type().Convert(ctx, given)
ret, inRange, err := i.Type().Convert(ctx, given)
if err == nil && !inRange {
err = sql.ErrValueOutOfRange.New(given, i.Type())
}
if err != nil {
return nil, err
}
Expand Down
Loading
Loading