diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4e7a7062..60dc85139 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,6 +141,19 @@ jobs: --health-start-period 10s --health-timeout 5s --health-retries 10 + maria103: + image: mariadb:10.3.13 + env: + MYSQL_DATABASE: test + MYSQL_ROOT_PASSWORD: pass + ports: + - 4308:3306 + options: >- + --health-cmd "mysqladmin ping -ppass" + --health-interval 10s + --health-start-period 10s + --health-timeout 5s + --health-retries 10 postgres10: image: postgres:10 env: @@ -280,6 +293,19 @@ jobs: --health-start-period 10s --health-timeout 5s --health-retries 10 + maria103: + image: mariadb:10.3.13 + env: + MYSQL_DATABASE: test + MYSQL_ROOT_PASSWORD: pass + ports: + - 4308:3306 + options: >- + --health-cmd "mysqladmin ping -ppass" + --health-interval 10s + --health-start-period 10s + --health-timeout 5s + --health-retries 10 postgres10: image: postgres:10 env: diff --git a/dialect/sql/schema/mysql.go b/dialect/sql/schema/mysql.go index 738dcd8ac..bd35651e3 100644 --- a/dialect/sql/schema/mysql.go +++ b/dialect/sql/schema/mysql.go @@ -6,7 +6,6 @@ package schema import ( "context" - "database/sql/driver" "fmt" "math" "strconv" @@ -558,26 +557,22 @@ func (d *MySQL) alterColumns(table string, add, modify, drop []*Column) sql.Quer // normalizeJSON normalize MariaDB longtext columns to type JSON. func (d *MySQL) normalizeJSON(ctx context.Context, tx dialect.Tx, t *Table) error { - var ( - names []driver.Value - columns = make(map[string]*Column) - ) + columns := make(map[string]*Column) for _, c := range t.Columns { if c.typ == "longtext" { columns[c.Name] = c - names = append(names, c.Name) } } - if len(names) == 0 { + if len(columns) == 0 { return nil } rows := &sql.Rows{} - query, args := sql.Select("CONSTRAINT_NAME", "CHECK_CLAUSE"). + query, args := sql.Select("CONSTRAINT_NAME"). From(sql.Table("CHECK_CONSTRAINTS").Schema("INFORMATION_SCHEMA")). Where(sql.And( d.matchSchema("CONSTRAINT_SCHEMA"), sql.EQ("TABLE_NAME", t.Name), - sql.InValues("CONSTRAINT_NAME", names...), + sql.Like("CHECK_CLAUSE", "json_valid(%)"), )). Query() if err := tx.Query(ctx, query, args, rows); err != nil { @@ -585,21 +580,23 @@ func (d *MySQL) normalizeJSON(ctx context.Context, tx dialect.Tx, t *Table) erro } // Call Close in cases of failures (Close is idempotent). defer rows.Close() - for rows.Next() { - var name, check string - if err := rows.Scan(&name, &check); err != nil { - return fmt.Errorf("mysql: scan table constraints") - } - c, ok := columns[name] - if !ok || !strings.HasPrefix(check, "json_valid") { - continue - } - c.Type = field.TypeJSON + names := make([]string, 0, len(columns)) + if err := sql.ScanSlice(rows, &names); err != nil { + return fmt.Errorf("mysql: scan table constraints: %w", err) } if err := rows.Err(); err != nil { return err } - return rows.Close() + if err := rows.Close(); err != nil { + return err + } + for _, name := range names { + c, ok := columns[name] + if ok { + c.Type = field.TypeJSON + } + } + return nil } // mariadb reports if the migration runs on MariaDB and returns the semver string. diff --git a/dialect/sql/schema/mysql_test.go b/dialect/sql/schema/mysql_test.go index 5a7bc2bfe..836bcdb46 100644 --- a/dialect/sql/schema/mysql_test.go +++ b/dialect/sql/schema/mysql_test.go @@ -1084,6 +1084,28 @@ func TestMySQL_Create(t *testing.T) { mock.ExpectCommit() }, }, + { + name: "mariadb/10.3.13/create table", + tables: []*Table{ + { + Name: "users", + Columns: []*Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + {Name: "json", Type: field.TypeJSON, Nullable: true}, + }, + PrimaryKey: []*Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + }, + }, + }, + before: func(mock mysqlMock) { + mock.start("10.3.13-MariaDB-1:10.3.13+maria~bionic") + mock.tableExists("users", false) + mock.ExpectExec(escape("CREATE TABLE IF NOT EXISTS `users`(`id` bigint AUTO_INCREMENT NOT NULL, `json` json NULL CHECK (JSON_VALID(`json`)), PRIMARY KEY(`id`)) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin")). + WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectCommit() + }, + }, { name: "mariadb/10.5.8/create table", tables: []*Table{ @@ -1136,10 +1158,10 @@ func TestMySQL_Create(t *testing.T) { WithArgs("users"). WillReturnRows(sqlmock.NewRows([]string{"index_name", "column_name", "non_unique", "seq_in_index"}). AddRow("PRIMARY", "id", "0", "1")) - mock.ExpectQuery(escape("SELECT `CONSTRAINT_NAME`, `CHECK_CLAUSE` FROM `INFORMATION_SCHEMA`.`CHECK_CONSTRAINTS` WHERE `CONSTRAINT_SCHEMA` = (SELECT DATABASE()) AND `TABLE_NAME` = ? AND `CONSTRAINT_NAME` IN (?, ?)")). - WithArgs("users", "json", "longtext"). - WillReturnRows(sqlmock.NewRows([]string{"CONSTRAINT_NAME", "CHECK_CLAUSE"}). - AddRow("json", "json_valid(`json`)")) + mock.ExpectQuery(escape("SELECT `CONSTRAINT_NAME` FROM `INFORMATION_SCHEMA`.`CHECK_CONSTRAINTS` WHERE `CONSTRAINT_SCHEMA` = (SELECT DATABASE()) AND `TABLE_NAME` = ? AND `CHECK_CLAUSE` LIKE ?")). + WithArgs("users", "json_valid(%)"). + WillReturnRows(sqlmock.NewRows([]string{"CONSTRAINT_NAME"}). + AddRow("json")) mock.ExpectCommit() }, }, diff --git a/entc/integration/compose/docker-compose.yaml b/entc/integration/compose/docker-compose.yaml index 43d25e96b..d3bff21e7 100644 --- a/entc/integration/compose/docker-compose.yaml +++ b/entc/integration/compose/docker-compose.yaml @@ -61,6 +61,17 @@ services: ports: - 4307:3306 + mariadb103: + platform: linux/amd64 + image: mariadb:10.3.13 + environment: + MYSQL_DATABASE: test + MYSQL_ROOT_PASSWORD: pass + healthcheck: + test: mysqladmin ping -ppass + ports: + - 4308:3306 + postgres10: platform: linux/amd64 image: postgres:10 diff --git a/entc/integration/ent/entql.go b/entc/integration/ent/entql.go index c6393d5f2..c1f043e0f 100644 --- a/entc/integration/ent/entql.go +++ b/entc/integration/ent/entql.go @@ -126,6 +126,7 @@ var schemaGraph = func() *sqlgraph.Schema { fieldtype.FieldRole: {Type: field.TypeEnum, Column: fieldtype.FieldRole}, fieldtype.FieldMAC: {Type: field.TypeString, Column: fieldtype.FieldMAC}, fieldtype.FieldUUID: {Type: field.TypeUUID, Column: fieldtype.FieldUUID}, + fieldtype.FieldStrings: {Type: field.TypeJSON, Column: fieldtype.FieldStrings}, }, } graph.Nodes[3] = &sqlgraph.Node{ @@ -1060,6 +1061,11 @@ func (f *FieldTypeFilter) WhereUUID(p entql.ValueP) { f.Where(p.Field(fieldtype.FieldUUID)) } +// WhereStrings applies the entql json.RawMessage predicate on the strings field. +func (f *FieldTypeFilter) WhereStrings(p entql.BytesP) { + f.Where(p.Field(fieldtype.FieldStrings)) +} + // addPredicate implements the predicateAdder interface. func (fq *FileQuery) addPredicate(pred func(s *sql.Selector)) { fq.predicates = append(fq.predicates, pred) diff --git a/entc/integration/ent/fieldtype.go b/entc/integration/ent/fieldtype.go index 0c87cf509..250f15854 100644 --- a/entc/integration/ent/fieldtype.go +++ b/entc/integration/ent/fieldtype.go @@ -7,6 +7,7 @@ package ent import ( + "encoding/json" "fmt" "net" "net/http" @@ -122,7 +123,9 @@ type FieldType struct { // MAC holds the value of the "mac" field. MAC schema.MAC `json:"mac,omitempty"` // UUID holds the value of the "uuid" field. - UUID uuid.UUID `json:"uuid,omitempty"` + UUID uuid.UUID `json:"uuid,omitempty"` + // Strings holds the value of the "strings" field. + Strings []string `json:"strings,omitempty"` file_field *int } @@ -131,7 +134,7 @@ func (*FieldType) scanValues(columns []string) ([]interface{}, error) { values := make([]interface{}, len(columns)) for i := range columns { switch columns[i] { - case fieldtype.FieldIP: + case fieldtype.FieldIP, fieldtype.FieldStrings: values[i] = &[]byte{} case fieldtype.FieldLink, fieldtype.FieldLinkOther, fieldtype.FieldNullLink: values[i] = &schema.Link{} @@ -473,6 +476,15 @@ func (ft *FieldType) assignValues(columns []string, values []interface{}) error } else if value != nil { ft.UUID = *value } + case fieldtype.FieldStrings: + + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field strings", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &ft.Strings); err != nil { + return fmt.Errorf("unmarshal field strings: %w", err) + } + } case fieldtype.ForeignKeys[0]: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for edge-field file_field", value) @@ -624,6 +636,8 @@ func (ft *FieldType) String() string { builder.WriteString(fmt.Sprintf("%v", ft.MAC)) builder.WriteString(", uuid=") builder.WriteString(fmt.Sprintf("%v", ft.UUID)) + builder.WriteString(", strings=") + builder.WriteString(fmt.Sprintf("%v", ft.Strings)) builder.WriteByte(')') return builder.String() } diff --git a/entc/integration/ent/fieldtype/fieldtype.go b/entc/integration/ent/fieldtype/fieldtype.go index 1b8c660ae..6e87dc91c 100644 --- a/entc/integration/ent/fieldtype/fieldtype.go +++ b/entc/integration/ent/fieldtype/fieldtype.go @@ -117,6 +117,8 @@ const ( FieldMAC = "mac" // FieldUUID holds the string denoting the uuid field in the database. FieldUUID = "uuid" + // FieldStrings holds the string denoting the strings field in the database. + FieldStrings = "strings" // Table holds the table name of the fieldtype in the database. Table = "field_types" ) @@ -173,6 +175,7 @@ var Columns = []string{ FieldRole, FieldMAC, FieldUUID, + FieldStrings, } // ForeignKeys holds the SQL foreign-keys that are owned by the "field_types" diff --git a/entc/integration/ent/fieldtype/where.go b/entc/integration/ent/fieldtype/where.go index 1e46985ac..1891035dd 100644 --- a/entc/integration/ent/fieldtype/where.go +++ b/entc/integration/ent/fieldtype/where.go @@ -4661,6 +4661,20 @@ func UUIDNotNil() predicate.FieldType { }) } +// StringsIsNil applies the IsNil predicate on the "strings" field. +func StringsIsNil() predicate.FieldType { + return predicate.FieldType(func(s *sql.Selector) { + s.Where(sql.IsNull(s.C(FieldStrings))) + }) +} + +// StringsNotNil applies the NotNil predicate on the "strings" field. +func StringsNotNil() predicate.FieldType { + return predicate.FieldType(func(s *sql.Selector) { + s.Where(sql.NotNull(s.C(FieldStrings))) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.FieldType) predicate.FieldType { return predicate.FieldType(func(s *sql.Selector) { diff --git a/entc/integration/ent/fieldtype_create.go b/entc/integration/ent/fieldtype_create.go index 184e9e4f8..e87c92c3d 100644 --- a/entc/integration/ent/fieldtype_create.go +++ b/entc/integration/ent/fieldtype_create.go @@ -580,6 +580,12 @@ func (ftc *FieldTypeCreate) SetUUID(u uuid.UUID) *FieldTypeCreate { return ftc } +// SetStrings sets the "strings" field. +func (ftc *FieldTypeCreate) SetStrings(s []string) *FieldTypeCreate { + ftc.mutation.SetStrings(s) + return ftc +} + // Mutation returns the FieldTypeMutation object of the builder. func (ftc *FieldTypeCreate) Mutation() *FieldTypeMutation { return ftc.mutation @@ -1119,6 +1125,14 @@ func (ftc *FieldTypeCreate) createSpec() (*FieldType, *sqlgraph.CreateSpec) { }) _node.UUID = value } + if value, ok := ftc.mutation.Strings(); ok { + _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ + Type: field.TypeJSON, + Value: value, + Column: fieldtype.FieldStrings, + }) + _node.Strings = value + } return _node, _spec } diff --git a/entc/integration/ent/fieldtype_update.go b/entc/integration/ent/fieldtype_update.go index 51a79ad55..5219bd34b 100644 --- a/entc/integration/ent/fieldtype_update.go +++ b/entc/integration/ent/fieldtype_update.go @@ -1056,6 +1056,18 @@ func (ftu *FieldTypeUpdate) ClearUUID() *FieldTypeUpdate { return ftu } +// SetStrings sets the "strings" field. +func (ftu *FieldTypeUpdate) SetStrings(s []string) *FieldTypeUpdate { + ftu.mutation.SetStrings(s) + return ftu +} + +// ClearStrings clears the value of the "strings" field. +func (ftu *FieldTypeUpdate) ClearStrings() *FieldTypeUpdate { + ftu.mutation.ClearStrings() + return ftu +} + // Mutation returns the FieldTypeMutation object of the builder. func (ftu *FieldTypeUpdate) Mutation() *FieldTypeMutation { return ftu.mutation @@ -1982,6 +1994,19 @@ func (ftu *FieldTypeUpdate) sqlSave(ctx context.Context) (n int, err error) { Column: fieldtype.FieldUUID, }) } + if value, ok := ftu.mutation.Strings(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeJSON, + Value: value, + Column: fieldtype.FieldStrings, + }) + } + if ftu.mutation.StringsCleared() { + _spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{ + Type: field.TypeJSON, + Column: fieldtype.FieldStrings, + }) + } if n, err = sqlgraph.UpdateNodes(ctx, ftu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{fieldtype.Label} @@ -3021,6 +3046,18 @@ func (ftuo *FieldTypeUpdateOne) ClearUUID() *FieldTypeUpdateOne { return ftuo } +// SetStrings sets the "strings" field. +func (ftuo *FieldTypeUpdateOne) SetStrings(s []string) *FieldTypeUpdateOne { + ftuo.mutation.SetStrings(s) + return ftuo +} + +// ClearStrings clears the value of the "strings" field. +func (ftuo *FieldTypeUpdateOne) ClearStrings() *FieldTypeUpdateOne { + ftuo.mutation.ClearStrings() + return ftuo +} + // Mutation returns the FieldTypeMutation object of the builder. func (ftuo *FieldTypeUpdateOne) Mutation() *FieldTypeMutation { return ftuo.mutation @@ -3971,6 +4008,19 @@ func (ftuo *FieldTypeUpdateOne) sqlSave(ctx context.Context) (_node *FieldType, Column: fieldtype.FieldUUID, }) } + if value, ok := ftuo.mutation.Strings(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeJSON, + Value: value, + Column: fieldtype.FieldStrings, + }) + } + if ftuo.mutation.StringsCleared() { + _spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{ + Type: field.TypeJSON, + Column: fieldtype.FieldStrings, + }) + } _node = &FieldType{config: ftuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/entc/integration/ent/migrate/schema.go b/entc/integration/ent/migrate/schema.go index de67eb218..d27bbba55 100644 --- a/entc/integration/ent/migrate/schema.go +++ b/entc/integration/ent/migrate/schema.go @@ -120,6 +120,7 @@ var ( {Name: "role", Type: field.TypeEnum, Enums: []string{"ADMIN", "OWNER", "USER", "READ", "WRITE"}, Default: "READ"}, {Name: "mac", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "macaddr"}}, {Name: "uuid", Type: field.TypeUUID, Nullable: true}, + {Name: "strings", Type: field.TypeJSON, Nullable: true}, {Name: "file_field", Type: field.TypeInt, Nullable: true}, } // FieldTypesTable holds the schema information for the "field_types" table. @@ -130,7 +131,7 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "field_types_files_field", - Columns: []*schema.Column{FieldTypesColumns[50]}, + Columns: []*schema.Column{FieldTypesColumns[51]}, RefColumns: []*schema.Column{FilesColumns[0]}, OnDelete: schema.SetNull, }, diff --git a/entc/integration/ent/mutation.go b/entc/integration/ent/mutation.go index 6fc4c92dd..da72b4621 100644 --- a/entc/integration/ent/mutation.go +++ b/entc/integration/ent/mutation.go @@ -1377,6 +1377,7 @@ type FieldTypeMutation struct { role *role.Role mac *schema.MAC uuid *uuid.UUID + strings *[]string clearedFields map[string]struct{} done bool oldValue func(context.Context) (*FieldType, error) @@ -4410,6 +4411,55 @@ func (m *FieldTypeMutation) ResetUUID() { delete(m.clearedFields, fieldtype.FieldUUID) } +// SetStrings sets the "strings" field. +func (m *FieldTypeMutation) SetStrings(s []string) { + m.strings = &s +} + +// Strings returns the value of the "strings" field in the mutation. +func (m *FieldTypeMutation) Strings() (r []string, exists bool) { + v := m.strings + if v == nil { + return + } + return *v, true +} + +// OldStrings returns the old "strings" field's value of the FieldType entity. +// If the FieldType object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *FieldTypeMutation) OldStrings(ctx context.Context) (v []string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, fmt.Errorf("OldStrings is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, fmt.Errorf("OldStrings requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldStrings: %w", err) + } + return oldValue.Strings, nil +} + +// ClearStrings clears the value of the "strings" field. +func (m *FieldTypeMutation) ClearStrings() { + m.strings = nil + m.clearedFields[fieldtype.FieldStrings] = struct{}{} +} + +// StringsCleared returns if the "strings" field was cleared in this mutation. +func (m *FieldTypeMutation) StringsCleared() bool { + _, ok := m.clearedFields[fieldtype.FieldStrings] + return ok +} + +// ResetStrings resets all changes to the "strings" field. +func (m *FieldTypeMutation) ResetStrings() { + m.strings = nil + delete(m.clearedFields, fieldtype.FieldStrings) +} + // Op returns the operation name. func (m *FieldTypeMutation) Op() Op { return m.op @@ -4424,7 +4474,7 @@ func (m *FieldTypeMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *FieldTypeMutation) Fields() []string { - fields := make([]string, 0, 49) + fields := make([]string, 0, 50) if m.int != nil { fields = append(fields, fieldtype.FieldInt) } @@ -4572,6 +4622,9 @@ func (m *FieldTypeMutation) Fields() []string { if m.uuid != nil { fields = append(fields, fieldtype.FieldUUID) } + if m.strings != nil { + fields = append(fields, fieldtype.FieldStrings) + } return fields } @@ -4678,6 +4731,8 @@ func (m *FieldTypeMutation) Field(name string) (ent.Value, bool) { return m.MAC() case fieldtype.FieldUUID: return m.UUID() + case fieldtype.FieldStrings: + return m.Strings() } return nil, false } @@ -4785,6 +4840,8 @@ func (m *FieldTypeMutation) OldField(ctx context.Context, name string) (ent.Valu return m.OldMAC(ctx) case fieldtype.FieldUUID: return m.OldUUID(ctx) + case fieldtype.FieldStrings: + return m.OldStrings(ctx) } return nil, fmt.Errorf("unknown FieldType field %s", name) } @@ -5137,6 +5194,13 @@ func (m *FieldTypeMutation) SetField(name string, value ent.Value) error { } m.SetUUID(v) return nil + case fieldtype.FieldStrings: + v, ok := value.([]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetStrings(v) + return nil } return fmt.Errorf("unknown FieldType field %s", name) } @@ -5659,6 +5723,9 @@ func (m *FieldTypeMutation) ClearedFields() []string { if m.FieldCleared(fieldtype.FieldUUID) { fields = append(fields, fieldtype.FieldUUID) } + if m.FieldCleared(fieldtype.FieldStrings) { + fields = append(fields, fieldtype.FieldStrings) + } return fields } @@ -5802,6 +5869,9 @@ func (m *FieldTypeMutation) ClearField(name string) error { case fieldtype.FieldUUID: m.ClearUUID() return nil + case fieldtype.FieldStrings: + m.ClearStrings() + return nil } return fmt.Errorf("unknown FieldType nullable field %s", name) } @@ -5957,6 +6027,9 @@ func (m *FieldTypeMutation) ResetField(name string) error { case fieldtype.FieldUUID: m.ResetUUID() return nil + case fieldtype.FieldStrings: + m.ResetStrings() + return nil } return fmt.Errorf("unknown FieldType field %s", name) } diff --git a/entc/integration/ent/schema/fieldtype.go b/entc/integration/ent/schema/fieldtype.go index 446d6b44e..4e0f8c060 100644 --- a/entc/integration/ent/schema/fieldtype.go +++ b/entc/integration/ent/schema/fieldtype.go @@ -162,6 +162,8 @@ func (FieldType) Fields() []ent.Field { //nolint:funlen }), field.UUID("uuid", uuid.UUID{}). Optional(), + field.Strings("strings"). + Optional(), } } diff --git a/entc/integration/gremlin/ent/fieldtype.go b/entc/integration/gremlin/ent/fieldtype.go index 6df08c422..163e79cc9 100644 --- a/entc/integration/gremlin/ent/fieldtype.go +++ b/entc/integration/gremlin/ent/fieldtype.go @@ -124,6 +124,8 @@ type FieldType struct { MAC schema.MAC `json:"mac,omitempty"` // UUID holds the value of the "uuid" field. UUID uuid.UUID `json:"uuid,omitempty"` + // Strings holds the value of the "strings" field. + Strings []string `json:"strings,omitempty"` } // FromResponse scans the gremlin response data into FieldType. @@ -183,6 +185,7 @@ func (ft *FieldType) FromResponse(res *gremlin.Response) error { Role role.Role `json:"role,omitempty"` MAC schema.MAC `json:"mac,omitempty"` UUID uuid.UUID `json:"uuid,omitempty"` + Strings []string `json:"strings,omitempty"` } if err := vmap.Decode(&scanft); err != nil { return err @@ -237,6 +240,7 @@ func (ft *FieldType) FromResponse(res *gremlin.Response) error { ft.Role = scanft.Role ft.MAC = scanft.MAC ft.UUID = scanft.UUID + ft.Strings = scanft.Strings return nil } @@ -379,6 +383,8 @@ func (ft *FieldType) String() string { builder.WriteString(fmt.Sprintf("%v", ft.MAC)) builder.WriteString(", uuid=") builder.WriteString(fmt.Sprintf("%v", ft.UUID)) + builder.WriteString(", strings=") + builder.WriteString(fmt.Sprintf("%v", ft.Strings)) builder.WriteByte(')') return builder.String() } @@ -443,6 +449,7 @@ func (ft *FieldTypes) FromResponse(res *gremlin.Response) error { Role role.Role `json:"role,omitempty"` MAC schema.MAC `json:"mac,omitempty"` UUID uuid.UUID `json:"uuid,omitempty"` + Strings []string `json:"strings,omitempty"` } if err := vmap.Decode(&scanft); err != nil { return err @@ -499,6 +506,7 @@ func (ft *FieldTypes) FromResponse(res *gremlin.Response) error { Role: v.Role, MAC: v.MAC, UUID: v.UUID, + Strings: v.Strings, }) } return nil diff --git a/entc/integration/gremlin/ent/fieldtype/fieldtype.go b/entc/integration/gremlin/ent/fieldtype/fieldtype.go index a9af0e5dc..3e476a4d1 100644 --- a/entc/integration/gremlin/ent/fieldtype/fieldtype.go +++ b/entc/integration/gremlin/ent/fieldtype/fieldtype.go @@ -117,6 +117,8 @@ const ( FieldMAC = "mac" // FieldUUID holds the string denoting the uuid field in the database. FieldUUID = "uuid" + // FieldStrings holds the string denoting the strings field in the database. + FieldStrings = "strings" ) var ( diff --git a/entc/integration/gremlin/ent/fieldtype/where.go b/entc/integration/gremlin/ent/fieldtype/where.go index 562ae3643..7300cdc3c 100644 --- a/entc/integration/gremlin/ent/fieldtype/where.go +++ b/entc/integration/gremlin/ent/fieldtype/where.go @@ -4024,6 +4024,20 @@ func UUIDNotNil() predicate.FieldType { }) } +// StringsIsNil applies the IsNil predicate on the "strings" field. +func StringsIsNil() predicate.FieldType { + return predicate.FieldType(func(t *dsl.Traversal) { + t.HasLabel(Label).HasNot(FieldStrings) + }) +} + +// StringsNotNil applies the NotNil predicate on the "strings" field. +func StringsNotNil() predicate.FieldType { + return predicate.FieldType(func(t *dsl.Traversal) { + t.HasLabel(Label).Has(FieldStrings) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.FieldType) predicate.FieldType { return predicate.FieldType(func(tr *dsl.Traversal) { diff --git a/entc/integration/gremlin/ent/fieldtype_create.go b/entc/integration/gremlin/ent/fieldtype_create.go index 975b3989b..ab51b5e05 100644 --- a/entc/integration/gremlin/ent/fieldtype_create.go +++ b/entc/integration/gremlin/ent/fieldtype_create.go @@ -581,6 +581,12 @@ func (ftc *FieldTypeCreate) SetUUID(u uuid.UUID) *FieldTypeCreate { return ftc } +// SetStrings sets the "strings" field. +func (ftc *FieldTypeCreate) SetStrings(s []string) *FieldTypeCreate { + ftc.mutation.SetStrings(s) + return ftc +} + // Mutation returns the FieldTypeMutation object of the builder. func (ftc *FieldTypeCreate) Mutation() *FieldTypeMutation { return ftc.mutation @@ -869,6 +875,9 @@ func (ftc *FieldTypeCreate) gremlin() *dsl.Traversal { if value, ok := ftc.mutation.UUID(); ok { v.Property(dsl.Single, fieldtype.FieldUUID, value) } + if value, ok := ftc.mutation.Strings(); ok { + v.Property(dsl.Single, fieldtype.FieldStrings, value) + } return v.ValueMap(true) } diff --git a/entc/integration/gremlin/ent/fieldtype_update.go b/entc/integration/gremlin/ent/fieldtype_update.go index af547bc4d..a300369e2 100644 --- a/entc/integration/gremlin/ent/fieldtype_update.go +++ b/entc/integration/gremlin/ent/fieldtype_update.go @@ -1058,6 +1058,18 @@ func (ftu *FieldTypeUpdate) ClearUUID() *FieldTypeUpdate { return ftu } +// SetStrings sets the "strings" field. +func (ftu *FieldTypeUpdate) SetStrings(s []string) *FieldTypeUpdate { + ftu.mutation.SetStrings(s) + return ftu +} + +// ClearStrings clears the value of the "strings" field. +func (ftu *FieldTypeUpdate) ClearStrings() *FieldTypeUpdate { + ftu.mutation.ClearStrings() + return ftu +} + // Mutation returns the FieldTypeMutation object of the builder. func (ftu *FieldTypeUpdate) Mutation() *FieldTypeMutation { return ftu.mutation @@ -1412,6 +1424,9 @@ func (ftu *FieldTypeUpdate) gremlin() *dsl.Traversal { if value, ok := ftu.mutation.UUID(); ok { v.Property(dsl.Single, fieldtype.FieldUUID, value) } + if value, ok := ftu.mutation.Strings(); ok { + v.Property(dsl.Single, fieldtype.FieldStrings, value) + } var properties []interface{} if ftu.mutation.OptionalIntCleared() { properties = append(properties, fieldtype.FieldOptionalInt) @@ -1542,6 +1557,9 @@ func (ftu *FieldTypeUpdate) gremlin() *dsl.Traversal { if ftu.mutation.UUIDCleared() { properties = append(properties, fieldtype.FieldUUID) } + if ftu.mutation.StringsCleared() { + properties = append(properties, fieldtype.FieldStrings) + } if len(properties) > 0 { v.SideEffect(__.Properties(properties...).Drop()) } @@ -2578,6 +2596,18 @@ func (ftuo *FieldTypeUpdateOne) ClearUUID() *FieldTypeUpdateOne { return ftuo } +// SetStrings sets the "strings" field. +func (ftuo *FieldTypeUpdateOne) SetStrings(s []string) *FieldTypeUpdateOne { + ftuo.mutation.SetStrings(s) + return ftuo +} + +// ClearStrings clears the value of the "strings" field. +func (ftuo *FieldTypeUpdateOne) ClearStrings() *FieldTypeUpdateOne { + ftuo.mutation.ClearStrings() + return ftuo +} + // Mutation returns the FieldTypeMutation object of the builder. func (ftuo *FieldTypeUpdateOne) Mutation() *FieldTypeMutation { return ftuo.mutation @@ -2944,6 +2974,9 @@ func (ftuo *FieldTypeUpdateOne) gremlin(id string) *dsl.Traversal { if value, ok := ftuo.mutation.UUID(); ok { v.Property(dsl.Single, fieldtype.FieldUUID, value) } + if value, ok := ftuo.mutation.Strings(); ok { + v.Property(dsl.Single, fieldtype.FieldStrings, value) + } var properties []interface{} if ftuo.mutation.OptionalIntCleared() { properties = append(properties, fieldtype.FieldOptionalInt) @@ -3074,6 +3107,9 @@ func (ftuo *FieldTypeUpdateOne) gremlin(id string) *dsl.Traversal { if ftuo.mutation.UUIDCleared() { properties = append(properties, fieldtype.FieldUUID) } + if ftuo.mutation.StringsCleared() { + properties = append(properties, fieldtype.FieldStrings) + } if len(properties) > 0 { v.SideEffect(__.Properties(properties...).Drop()) } diff --git a/entc/integration/gremlin/ent/mutation.go b/entc/integration/gremlin/ent/mutation.go index 4824bf781..93bbbd939 100644 --- a/entc/integration/gremlin/ent/mutation.go +++ b/entc/integration/gremlin/ent/mutation.go @@ -1377,6 +1377,7 @@ type FieldTypeMutation struct { role *role.Role mac *schema.MAC uuid *uuid.UUID + strings *[]string clearedFields map[string]struct{} done bool oldValue func(context.Context) (*FieldType, error) @@ -4410,6 +4411,55 @@ func (m *FieldTypeMutation) ResetUUID() { delete(m.clearedFields, fieldtype.FieldUUID) } +// SetStrings sets the "strings" field. +func (m *FieldTypeMutation) SetStrings(s []string) { + m.strings = &s +} + +// Strings returns the value of the "strings" field in the mutation. +func (m *FieldTypeMutation) Strings() (r []string, exists bool) { + v := m.strings + if v == nil { + return + } + return *v, true +} + +// OldStrings returns the old "strings" field's value of the FieldType entity. +// If the FieldType object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *FieldTypeMutation) OldStrings(ctx context.Context) (v []string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, fmt.Errorf("OldStrings is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, fmt.Errorf("OldStrings requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldStrings: %w", err) + } + return oldValue.Strings, nil +} + +// ClearStrings clears the value of the "strings" field. +func (m *FieldTypeMutation) ClearStrings() { + m.strings = nil + m.clearedFields[fieldtype.FieldStrings] = struct{}{} +} + +// StringsCleared returns if the "strings" field was cleared in this mutation. +func (m *FieldTypeMutation) StringsCleared() bool { + _, ok := m.clearedFields[fieldtype.FieldStrings] + return ok +} + +// ResetStrings resets all changes to the "strings" field. +func (m *FieldTypeMutation) ResetStrings() { + m.strings = nil + delete(m.clearedFields, fieldtype.FieldStrings) +} + // Op returns the operation name. func (m *FieldTypeMutation) Op() Op { return m.op @@ -4424,7 +4474,7 @@ func (m *FieldTypeMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *FieldTypeMutation) Fields() []string { - fields := make([]string, 0, 49) + fields := make([]string, 0, 50) if m.int != nil { fields = append(fields, fieldtype.FieldInt) } @@ -4572,6 +4622,9 @@ func (m *FieldTypeMutation) Fields() []string { if m.uuid != nil { fields = append(fields, fieldtype.FieldUUID) } + if m.strings != nil { + fields = append(fields, fieldtype.FieldStrings) + } return fields } @@ -4678,6 +4731,8 @@ func (m *FieldTypeMutation) Field(name string) (ent.Value, bool) { return m.MAC() case fieldtype.FieldUUID: return m.UUID() + case fieldtype.FieldStrings: + return m.Strings() } return nil, false } @@ -4785,6 +4840,8 @@ func (m *FieldTypeMutation) OldField(ctx context.Context, name string) (ent.Valu return m.OldMAC(ctx) case fieldtype.FieldUUID: return m.OldUUID(ctx) + case fieldtype.FieldStrings: + return m.OldStrings(ctx) } return nil, fmt.Errorf("unknown FieldType field %s", name) } @@ -5137,6 +5194,13 @@ func (m *FieldTypeMutation) SetField(name string, value ent.Value) error { } m.SetUUID(v) return nil + case fieldtype.FieldStrings: + v, ok := value.([]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetStrings(v) + return nil } return fmt.Errorf("unknown FieldType field %s", name) } @@ -5659,6 +5723,9 @@ func (m *FieldTypeMutation) ClearedFields() []string { if m.FieldCleared(fieldtype.FieldUUID) { fields = append(fields, fieldtype.FieldUUID) } + if m.FieldCleared(fieldtype.FieldStrings) { + fields = append(fields, fieldtype.FieldStrings) + } return fields } @@ -5802,6 +5869,9 @@ func (m *FieldTypeMutation) ClearField(name string) error { case fieldtype.FieldUUID: m.ClearUUID() return nil + case fieldtype.FieldStrings: + m.ClearStrings() + return nil } return fmt.Errorf("unknown FieldType nullable field %s", name) } @@ -5957,6 +6027,9 @@ func (m *FieldTypeMutation) ResetField(name string) error { case fieldtype.FieldUUID: m.ResetUUID() return nil + case fieldtype.FieldStrings: + m.ResetStrings() + return nil } return fmt.Errorf("unknown FieldType field %s", name) } diff --git a/entc/integration/integration_test.go b/entc/integration/integration_test.go index 235c60202..82741a819 100644 --- a/entc/integration/integration_test.go +++ b/entc/integration/integration_test.go @@ -56,7 +56,6 @@ func TestSQLite(t *testing.T) { } func TestMySQL(t *testing.T) { - t.Parallel() for version, port := range map[string]int{"56": 3306, "57": 3307, "8": 3308} { addr := net.JoinHostPort("localhost", strconv.Itoa(port)) t.Run(version, func(t *testing.T) { @@ -74,13 +73,18 @@ func TestMySQL(t *testing.T) { } func TestMaria(t *testing.T) { - client := enttest.Open(t, dialect.MySQL, "root:pass@tcp(localhost:4306)/test?parseTime=True", opts) - defer client.Close() - for _, tt := range tests { - name := runtime.FuncForPC(reflect.ValueOf(tt).Pointer()).Name() - t.Run(name[strings.LastIndex(name, ".")+1:], func(t *testing.T) { - drop(t, client) - tt(t, client) + for version, port := range map[string]int{"10.5": 4306, "10.2": 4307, "10.3": 4308} { + t.Run(version, func(t *testing.T) { + addr := net.JoinHostPort("localhost", strconv.Itoa(port)) + client := enttest.Open(t, dialect.MySQL, fmt.Sprintf("root:pass@tcp(%s)/test?parseTime=True", addr), opts) + defer client.Close() + for _, tt := range tests { + name := runtime.FuncForPC(reflect.ValueOf(tt).Pointer()).Name() + t.Run(name[strings.LastIndex(name, ".")+1:], func(t *testing.T) { + drop(t, client) + tt(t, client) + }) + } }) } }