diff --git a/server/database.go b/server/database.go index c487bf2..cc3def5 100644 --- a/server/database.go +++ b/server/database.go @@ -762,7 +762,7 @@ func (d *database) write(ctx context.Context, tx *transaction, tbl string, cols // Check not nullable columns are specified for Insert/Replace if nonNullCheck { - if exist, nonNullables := table.NonNullableColumnsExist(cols); exist { + if exist, nonNullables := table.NonNullableAndNonGeneratedColumnsExist(cols); exist { columns := strings.Join(nonNullables, ", ") return status.Errorf(codes.FailedPrecondition, "A new row in table %s does not specify a non-null value for these NOT NULL columns: %s", diff --git a/server/database_test.go b/server/database_test.go index 4c49891..55e42a6 100644 --- a/server/database_test.go +++ b/server/database_test.go @@ -35,7 +35,7 @@ import ( ) var ( - allSchema = []string{schemaSimple, schemaInterleaved, schemaInterleavedCascade, schemaInterleavedNoAction, schemaCompositePrimaryKeys, schemaFullTypes, schemaArrayTypes, schemaJoinA, schemaJoinB, schemaFromTable, schemaGeneratedValues, schemaDefaultValues} + allSchema = []string{schemaSimple, schemaInterleaved, schemaInterleavedCascade, schemaInterleavedNoAction, schemaCompositePrimaryKeys, schemaFullTypes, schemaArrayTypes, schemaJoinA, schemaJoinB, schemaFromTable, schemaGeneratedValues, schemaGeneratedColumn, schemaDefaultValues} schemaSimple = `CREATE TABLE Simple ( Id INT64 NOT NULL, Value STRING(MAX) NOT NULL, @@ -139,6 +139,11 @@ CREATE INDEX FullTypesByTimestamp ON FullTypes(FTTimestamp); "`JOIN` INT64 NOT NULL, " + ") PRIMARY KEY(`ALL`); \n" + "CREATE INDEX `ALL` ON `From`(`ALL`);" + schemaGeneratedColumn = `CREATE TABLE GeneratedColumn ( + Id INT64 NOT NULL, + Value STRING(MAX) NOT NULL AS (CAST(Id AS STRING)) STORED, +) PRIMARY KEY(Id); +` schemaGeneratedValues = `CREATE TABLE GeneratedValues ( Id INT64 NOT NULL, @@ -1698,6 +1703,18 @@ func TestInsertAndReplace(t *testing.T) { []interface{}{int64(2), int64(2), int64(2)}, }, }, + "GeneratedColumn": { + tbl: "GeneratedColumn", + wcols: []string{"Id"}, + values: []*structpb.Value{ + makeStringValue("100"), + }, + cols: []string{"Id", "Value"}, + limit: 100, + expected: [][]interface{}{ + []interface{}{int64(100), "100"}, + }, + }, } for _, op := range []string{"INSERT", "REPLACE"} { @@ -3789,6 +3806,7 @@ func TestInformationSchema(t *testing.T) { []interface{}{"", "", "DefaultValues", nil, nil, "COMMITTED"}, []interface{}{"", "", "From", nil, nil, "COMMITTED"}, []interface{}{"", "", "FullTypes", nil, nil, "COMMITTED"}, + []interface{}{"", "", "GeneratedColumn", nil, nil, "COMMITTED"}, []interface{}{"", "", "GeneratedValues", nil, nil, "COMMITTED"}, []interface{}{"", "", "Interleaved", "ParentTable", nil, "COMMITTED"}, []interface{}{"", "", "InterleavedCascade", "ParentTableCascade", "CASCADE", "COMMITTED"}, @@ -3941,6 +3959,8 @@ func TestInformationSchema(t *testing.T) { {"", "", "FullTypes", "FTFloatNull", int64(13), nil, nil, "YES", "FLOAT64"}, {"", "", "FullTypes", "FTDate", int64(14), nil, nil, "NO", "DATE"}, {"", "", "FullTypes", "FTDateNull", int64(15), nil, nil, "YES", "DATE"}, + {"", "", "GeneratedColumn", "Id", int64(1), nil, nil, "NO", "INT64"}, + {"", "", "GeneratedColumn", "Value", int64(2), nil, nil, "NO", "STRING(MAX)"}, {"", "", "GeneratedValues", "Id", int64(1), nil, nil, "NO", "INT64"}, {"", "", "GeneratedValues", "Value", int64(2), nil, nil, "NO", "STRING(32)"}, {"", "", "GeneratedValues", "N", int64(3), nil, nil, "NO", "INT64"}, @@ -4007,6 +4027,7 @@ func TestInformationSchema(t *testing.T) { {"", "", "DefaultValues", "PRIMARY_KEY", "PRIMARY_KEY", "", true, false, nil, false}, {"", "", "From", "PRIMARY_KEY", "PRIMARY_KEY", "", true, false, nil, false}, {"", "", "FullTypes", "PRIMARY_KEY", "PRIMARY_KEY", "", true, false, nil, false}, + {"", "", "GeneratedColumn", "PRIMARY_KEY", "PRIMARY_KEY", "", true, false, nil, false}, {"", "", "GeneratedValues", "PRIMARY_KEY", "PRIMARY_KEY", "", true, false, nil, false}, {"", "", "Interleaved", "PRIMARY_KEY", "PRIMARY_KEY", "", true, false, nil, false}, {"", "", "InterleavedCascade", "PRIMARY_KEY", "PRIMARY_KEY", "", true, false, nil, false}, @@ -4109,6 +4130,7 @@ func TestInformationSchema(t *testing.T) { {"", "", "FullTypes", "FullTypesByIntTimestamp", "INDEX", "FTTimestamp", int64(2), "ASC", "NO", "TIMESTAMP"}, {"", "", "FullTypes", "FullTypesByTimestamp", "INDEX", "FTTimestamp", int64(1), "ASC", "NO", "TIMESTAMP"}, {"", "", "FullTypes", "PRIMARY_KEY", "PRIMARY_KEY", "PKey", int64(1), "ASC", "NO", "STRING(32)"}, + {"", "", "GeneratedColumn", "PRIMARY_KEY", "PRIMARY_KEY", "Id", int64(1), "ASC", "NO", "INT64"}, {"", "", "GeneratedValues", "PRIMARY_KEY", "PRIMARY_KEY", "Id", int64(1), "ASC", "NO", "INT64"}, {"", "", "Interleaved", "InterleavedKey", "INDEX", "Id", int64(1), "ASC", "NO", "INT64"}, {"", "", "Interleaved", "InterleavedKey", "INDEX", "Value", int64(2), "ASC", "NO", "STRING(MAX)"}, diff --git a/server/query.go b/server/query.go index a0986e1..5b3a0ac 100644 --- a/server/query.go +++ b/server/query.go @@ -480,7 +480,7 @@ func (b *QueryBuilder) buildInsert(up *ast.Insert) (string, []interface{}, error } // Check not nullable columns - if exist, nonNullables := t.NonNullableColumnsExist(columns); exist { + if exist, nonNullables := t.NonNullableAndNonGeneratedColumnsExist(columns); exist { columns := strings.Join(nonNullables, ", ") return "", nil, status.Errorf(codes.FailedPrecondition, "A new row in table %s does not specify a non-null value for these NOT NULL columns: %s", diff --git a/server/table.go b/server/table.go index b7ba760..b9be13e 100644 --- a/server/table.go +++ b/server/table.go @@ -60,28 +60,31 @@ func (t *Table) TableViewWithAlias(alias string) *TableView { return createTableViewFromTable(t, alias) } -// NonNullableColumnsExist checks non nullable columns exist in the spciefied columns. -// It returns true and the columns if non nullable columns exist. -func (t *Table) NonNullableColumnsExist(columns []string) (bool, []string) { +// NonNullableAndNonGeneratedColumnsExist checks non nullable columns exist in the spciefied columns. +// It returns true and the columns if non nullable and non generated columns exist. +func (t *Table) NonNullableAndNonGeneratedColumnsExist(columns []string) (bool, []string) { usedColumns := make(map[string]struct{}, len(columns)) for _, name := range columns { usedColumns[name] = struct{}{} } - var noExsitNonNullableColumns []string + var noExistNonNullableColumns []string for _, c := range t.columns { if c.nullable { continue } + if c.ast != nil && c.ast.GeneratedExpr != nil { + continue + } n := c.Name() if _, ok := usedColumns[n]; !ok { - noExsitNonNullableColumns = append(noExsitNonNullableColumns, n) + noExistNonNullableColumns = append(noExistNonNullableColumns, n) } } - if len(noExsitNonNullableColumns) > 0 { - return true, noExsitNonNullableColumns + if len(noExistNonNullableColumns) > 0 { + return true, noExistNonNullableColumns } return false, nil