diff --git a/doc/md/schema-indexes.md b/doc/md/schema-indexes.md index cc369f64a..2450ecfe8 100755 --- a/doc/md/schema-indexes.md +++ b/doc/md/schema-indexes.md @@ -5,6 +5,151 @@ title: Indexes ## Multiple Fields -## From Edges +Indexes can be configured on one or more fields in order to improve +speed of data retrieval, or defining uniqueness. + +```go +package schema + +import ( + "github.com/facebookincubator/ent" + "github.com/facebookincubator/ent/schema/index" +) + +// User holds the schema definition for the User entity. +type User struct { + ent.Schema +} + +func (User) Indexes() []ent.Index { + return []ent.Index{ + // non-unique index. + index.Fields("field1", "field2"), + // unique index. + index.Fields("first_name", "last_name"). + Unique(), + } +} +``` + +Note that, for setting a single field as unique, use the `Unique` +method on the field builder as follows: + +```go +func (User) Fields() []ent.Field { + return []ent.Field{ + field.String("phone"). + Unique(), + } +} +``` + +## Index On Edges + +Indexes can be configured on composition of fields and edges. The main use-case +is setting uniqueness on fields under specific relation. Let's take an example: + +![er-city-streets](https://entgo.io/assets/er_city_streets.png) + +In the example above, we have a `City` with many `Street`s, and we want to set the +street name to be unique under each city. + +`ent/schema/city.go` +```go +// City holds the schema definition for the City entity. +type City struct { + ent.Schema +} + +// Fields of the City. +func (City) Fields() []ent.Field { + return []ent.Field{ + field.String("name"), + } +} + +// Edges of the City. +func (City) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("streets", Street.Type), + } +} +``` + +`ent/schema/street.go` +```go +// Street holds the schema definition for the Street entity. +type Street struct { + ent.Schema +} + +// Fields of the Street. +func (Street) Fields() []ent.Field { + return []ent.Field{ + field.String("name"), + } +} + +// Edges of the Street. +func (Street) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("city", City.Type). + Ref("streets"). + Unique(), + } +} + +// Indexes of the Street. +func (Street) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("name"). + Edges("city"). + Unique(), + } +} +``` + +`example.go` +```go +func Do(ctx context.Context, client *ent.Client) error { + // Unlike `Save`, `SaveX` panics if an error occurs. + tlv := client.City. + Create(). + SetName("TLV"). + SaveX(ctx) + nyc := client.City. + Create(). + SetName("NYC"). + SaveX(ctx) + // Add a street "ST" to "TLV". + client.Street. + Create(). + SetName("ST"). + SetCity(tlv). + SaveX(ctx) + // This operation will fail because "ST" + // is already created under "TLV". + _, err := client.Street. + Create(). + SetName("ST"). + SetCity(tlv). + Save(ctx) + if err == nil { + return fmt.Errorf("expecting creation to fail") + } + // Add a street "ST" to "NYC". + client.Street. + Create(). + SetName("ST"). + SetCity(nyc). + SaveX(ctx) + return nil +} +``` + +The full example exists in [GitHub](https://github.com/facebookincubator/ent/tree/master/examples/edgeindex). + +## Dialect Support + +Indexes are currently support SQL dialects, and does not support Gremlin. -## Unique diff --git a/doc/website/static/css/custom.css b/doc/website/static/css/custom.css index c6a8592ad..e5194b358 100755 --- a/doc/website/static/css/custom.css +++ b/doc/website/static/css/custom.css @@ -77,7 +77,7 @@ a { } #er-linked-list, #er-user-spouse, #er-tree, #er-following-followers, -#er-user-friends { +#er-user-friends, #er-city-streets { height: 230px; } diff --git a/examples/edgeindex/README.md b/examples/edgeindex/README.md new file mode 100644 index 000000000..e6b7d4c9c --- /dev/null +++ b/examples/edgeindex/README.md @@ -0,0 +1,10 @@ +# City-Street Unique Index Example + +In this example, we have a `City` with many `Street`s, and we want to set the +street name to be unique under each city. + +### Generate Assets + +```console +go generate ./... +``` diff --git a/examples/edgeindex/edgeindex.go b/examples/edgeindex/edgeindex.go new file mode 100644 index 000000000..133263abb --- /dev/null +++ b/examples/edgeindex/edgeindex.go @@ -0,0 +1,68 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +package main + +import ( + "context" + "fmt" + "log" + + "github.com/facebookincubator/ent/examples/edgeindex/ent" + + "github.com/facebookincubator/ent/dialect/sql" + _ "github.com/mattn/go-sqlite3" +) + +func main() { + db, err := sql.Open("sqlite3", "file:o2o2types?mode=memory&cache=shared&_fk=1") + if err != nil { + log.Fatalf("failed opening connection to sqlite: %v", err) + } + defer db.Close() + client := ent.NewClient(ent.Driver(db)) + ctx := context.Background() + // run the auto migration tool. + if err := client.Schema.Create(ctx); err != nil { + log.Fatalf("failed creating schema resources: %v", err) + } + if err := Do(ctx, client); err != nil { + log.Fatal(err) + } +} + +func Do(ctx context.Context, client *ent.Client) error { + // Unlike `Save`, `SaveX` panics if an error occurs. + tlv := client.City. + Create(). + SetName("TLV"). + SaveX(ctx) + nyc := client.City. + Create(). + SetName("NYC"). + SaveX(ctx) + // Add a street "ST" to "TLV". + client.Street. + Create(). + SetName("ST"). + SetCity(tlv). + SaveX(ctx) + // This operation will fail because "ST" + // is already created under "TLV". + _, err := client.Street. + Create(). + SetName("ST"). + SetCity(tlv). + Save(ctx) + if err == nil { + return fmt.Errorf("expecting creation to fail") + } + // Add a street "ST" to "NYC". + client.Street. + Create(). + SetName("ST"). + SetCity(nyc). + SaveX(ctx) + return nil +} diff --git a/examples/edgeindex/ent/city.go b/examples/edgeindex/ent/city.go new file mode 100644 index 000000000..4a055f01e --- /dev/null +++ b/examples/edgeindex/ent/city.go @@ -0,0 +1,95 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "bytes" + "fmt" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// City is the model entity for the City schema. +type City struct { + config + // ID of the ent. + ID int `json:"id,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` +} + +// FromRows scans the sql response data into City. +func (c *City) FromRows(rows *sql.Rows) error { + var vc struct { + ID int + Name sql.NullString + } + // the order here should be the same as in the `city.Columns`. + if err := rows.Scan( + &vc.ID, + &vc.Name, + ); err != nil { + return err + } + c.ID = vc.ID + c.Name = vc.Name.String + return nil +} + +// QueryStreets queries the streets edge of the City. +func (c *City) QueryStreets() *StreetQuery { + return (&CityClient{c.config}).QueryStreets(c) +} + +// Update returns a builder for updating this City. +// Note that, you need to call City.Unwrap() before calling this method, if this City +// was returned from a transaction, and the transaction was committed or rolled back. +func (c *City) Update() *CityUpdateOne { + return (&CityClient{c.config}).UpdateOne(c) +} + +// Unwrap unwraps the entity that was returned from a transaction after it was closed, +// so that all next queries will be executed through the driver which created the transaction. +func (c *City) Unwrap() *City { + tx, ok := c.config.driver.(*txDriver) + if !ok { + panic("ent: City is not a transactional entity") + } + c.config.driver = tx.drv + return c +} + +// String implements the fmt.Stringer. +func (c *City) String() string { + buf := bytes.NewBuffer(nil) + buf.WriteString("City(") + buf.WriteString(fmt.Sprintf("id=%v", c.ID)) + buf.WriteString(fmt.Sprintf(", name=%v", c.Name)) + buf.WriteString(")") + return buf.String() +} + +// Cities is a parsable slice of City. +type Cities []*City + +// FromRows scans the sql response data into Cities. +func (c *Cities) FromRows(rows *sql.Rows) error { + for rows.Next() { + vc := &City{} + if err := vc.FromRows(rows); err != nil { + return err + } + *c = append(*c, vc) + } + return nil +} + +func (c Cities) config(cfg config) { + for i := range c { + c[i].config = cfg + } +} diff --git a/examples/edgeindex/ent/city/city.go b/examples/edgeindex/ent/city/city.go new file mode 100644 index 000000000..72ac8dfa0 --- /dev/null +++ b/examples/edgeindex/ent/city/city.go @@ -0,0 +1,32 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package city + +const ( + // Label holds the string label denoting the city type in the database. + Label = "city" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldName holds the string denoting the name vertex property in the database. + FieldName = "name" + + // Table holds the table name of the city in the database. + Table = "cities" + // StreetsTable is the table the holds the streets relation/edge. + StreetsTable = "streets" + // StreetsInverseTable is the table name for the Street entity. + // It exists in this package in order to avoid circular dependency with the "street" package. + StreetsInverseTable = "streets" + // StreetsColumn is the table column denoting the streets relation/edge. + StreetsColumn = "city_id" +) + +// Columns holds all SQL columns are city fields. +var Columns = []string{ + FieldID, + FieldName, +} diff --git a/examples/edgeindex/ent/city/where.go b/examples/edgeindex/ent/city/where.go new file mode 100644 index 000000000..33db0b608 --- /dev/null +++ b/examples/edgeindex/ent/city/where.go @@ -0,0 +1,316 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package city + +import ( + "github.com/facebookincubator/ent/examples/edgeindex/ent/predicate" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// ID filters vertices based on their identifier. +func ID(id int) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldID), id)) + }, + ) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldID), id)) + }, + ) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldID), id)) + }, + ) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.GT(s.C(FieldID), id)) + }, + ) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.GTE(s.C(FieldID), id)) + }, + ) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.LT(s.C(FieldID), id)) + }, + ) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.LTE(s.C(FieldID), id)) + }, + ) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int) predicate.City { + return predicate.City( + func(s *sql.Selector) { + // if not arguments were provided, append the FALSE constants, + // since we can't apply "IN ()". This will make this predicate falsy. + if len(ids) == 0 { + s.Where(sql.False()) + return + } + v := make([]interface{}, len(ids)) + for i := range v { + v[i] = ids[i] + } + s.Where(sql.In(s.C(FieldID), v...)) + }, + ) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int) predicate.City { + return predicate.City( + func(s *sql.Selector) { + // if not arguments were provided, append the FALSE constants, + // since we can't apply "IN ()". This will make this predicate falsy. + if len(ids) == 0 { + s.Where(sql.False()) + return + } + v := make([]interface{}, len(ids)) + for i := range v { + v[i] = ids[i] + } + s.Where(sql.NotIn(s.C(FieldID), v...)) + }, + ) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldName), v)) + }, + ) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldName), v)) + }, + ) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldName), v)) + }, + ) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.GT(s.C(FieldName), v)) + }, + ) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.GTE(s.C(FieldName), v)) + }, + ) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.LT(s.C(FieldName), v)) + }, + ) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.LTE(s.C(FieldName), v)) + }, + ) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.City { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.City( + func(s *sql.Selector) { + // if not arguments were provided, append the FALSE constants, + // since we can't apply "IN ()". This will make this predicate falsy. + if len(vs) == 0 { + s.Where(sql.False()) + return + } + s.Where(sql.In(s.C(FieldName), v...)) + }, + ) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.City { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.City( + func(s *sql.Selector) { + // if not arguments were provided, append the FALSE constants, + // since we can't apply "IN ()". This will make this predicate falsy. + if len(vs) == 0 { + s.Where(sql.False()) + return + } + s.Where(sql.NotIn(s.C(FieldName), v...)) + }, + ) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.Contains(s.C(FieldName), v)) + }, + ) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.HasPrefix(s.C(FieldName), v)) + }, + ) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.HasSuffix(s.C(FieldName), v)) + }, + ) +} + +// NameContainsFold applies the ContainsFold predicate on the "name" field. +func NameContainsFold(v string) predicate.City { + return predicate.City( + func(s *sql.Selector) { + s.Where(sql.ContainsFold(s.C(FieldName), v)) + }, + ) +} + +// HasStreets applies the HasEdge predicate on the "streets" edge. +func HasStreets() predicate.City { + return predicate.City( + func(s *sql.Selector) { + t1 := s.Table() + s.Where( + sql.In( + t1.C(FieldID), + sql.Select(StreetsColumn). + From(sql.Table(StreetsTable)). + Where(sql.NotNull(StreetsColumn)), + ), + ) + }, + ) +} + +// HasStreetsWith applies the HasEdge predicate on the "streets" edge with a given conditions (other predicates). +func HasStreetsWith(preds ...predicate.Street) predicate.City { + return predicate.City( + func(s *sql.Selector) { + t1 := s.Table() + t2 := sql.Select(StreetsColumn).From(sql.Table(StreetsTable)) + for _, p := range preds { + p(t2) + } + s.Where(sql.In(t1.C(FieldID), t2)) + }, + ) +} + +// And groups list of predicates with the AND operator between them. +func And(predicates ...predicate.City) predicate.City { + return predicate.City( + func(s *sql.Selector) { + for _, p := range predicates { + p(s) + } + }, + ) +} + +// Or groups list of predicates with the OR operator between them. +func Or(predicates ...predicate.City) predicate.City { + return predicate.City( + func(s *sql.Selector) { + for i, p := range predicates { + if i > 0 { + s.Or() + } + p(s) + } + }, + ) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.City) predicate.City { + return predicate.City( + func(s *sql.Selector) { + p(s.Not()) + }, + ) +} diff --git a/examples/edgeindex/ent/city_create.go b/examples/edgeindex/ent/city_create.go new file mode 100644 index 000000000..4f89ae340 --- /dev/null +++ b/examples/edgeindex/ent/city_create.go @@ -0,0 +1,117 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "github.com/facebookincubator/ent/examples/edgeindex/ent/city" + "github.com/facebookincubator/ent/examples/edgeindex/ent/street" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// CityCreate is the builder for creating a City entity. +type CityCreate struct { + config + name *string + streets map[int]struct{} +} + +// SetName sets the name field. +func (cc *CityCreate) SetName(s string) *CityCreate { + cc.name = &s + return cc +} + +// AddStreetIDs adds the streets edge to Street by ids. +func (cc *CityCreate) AddStreetIDs(ids ...int) *CityCreate { + if cc.streets == nil { + cc.streets = make(map[int]struct{}) + } + for i := range ids { + cc.streets[ids[i]] = struct{}{} + } + return cc +} + +// AddStreets adds the streets edges to Street. +func (cc *CityCreate) AddStreets(s ...*Street) *CityCreate { + ids := make([]int, len(s)) + for i := range s { + ids[i] = s[i].ID + } + return cc.AddStreetIDs(ids...) +} + +// Save creates the City in the database. +func (cc *CityCreate) Save(ctx context.Context) (*City, error) { + if cc.name == nil { + return nil, errors.New("ent: missing required field \"name\"") + } + return cc.sqlSave(ctx) +} + +// SaveX calls Save and panics if Save returns an error. +func (cc *CityCreate) SaveX(ctx context.Context) *City { + v, err := cc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +func (cc *CityCreate) sqlSave(ctx context.Context) (*City, error) { + var ( + res sql.Result + c = &City{config: cc.config} + ) + tx, err := cc.driver.Tx(ctx) + if err != nil { + return nil, err + } + builder := sql.Insert(city.Table).Default(cc.driver.Dialect()) + if cc.name != nil { + builder.Set(city.FieldName, *cc.name) + c.Name = *cc.name + } + query, args := builder.Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return nil, rollback(tx, err) + } + id, err := res.LastInsertId() + if err != nil { + return nil, rollback(tx, err) + } + c.ID = int(id) + if len(cc.streets) > 0 { + p := sql.P() + for eid := range cc.streets { + p.Or().EQ(street.FieldID, eid) + } + query, args := sql.Update(city.StreetsTable). + Set(city.StreetsColumn, id). + Where(sql.And(p, sql.IsNull(city.StreetsColumn))). + Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return nil, rollback(tx, err) + } + affected, err := res.RowsAffected() + if err != nil { + return nil, rollback(tx, err) + } + if int(affected) < len(cc.streets) { + return nil, rollback(tx, &ErrConstraintFailed{msg: fmt.Sprintf("one of \"streets\" %v already connected to a different \"City\"", keys(cc.streets))}) + } + } + if err := tx.Commit(); err != nil { + return nil, err + } + return c, nil +} diff --git a/examples/edgeindex/ent/city_delete.go b/examples/edgeindex/ent/city_delete.go new file mode 100644 index 000000000..3c3166fae --- /dev/null +++ b/examples/edgeindex/ent/city_delete.go @@ -0,0 +1,65 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "context" + + "github.com/facebookincubator/ent/examples/edgeindex/ent/city" + "github.com/facebookincubator/ent/examples/edgeindex/ent/predicate" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// CityDelete is the builder for deleting a City entity. +type CityDelete struct { + config + predicates []predicate.City +} + +// Where adds a new predicate for the builder. +func (cd *CityDelete) Where(ps ...predicate.City) *CityDelete { + cd.predicates = append(cd.predicates, ps...) + return cd +} + +// Exec executes the deletion query. +func (cd *CityDelete) Exec(ctx context.Context) error { + return cd.sqlExec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (cd *CityDelete) ExecX(ctx context.Context) { + if err := cd.Exec(ctx); err != nil { + panic(err) + } +} + +func (cd *CityDelete) sqlExec(ctx context.Context) error { + var res sql.Result + selector := sql.Select().From(sql.Table(city.Table)) + for _, p := range cd.predicates { + p(selector) + } + query, args := sql.Delete(city.Table).FromSelect(selector).Query() + return cd.driver.Exec(ctx, query, args, &res) +} + +// CityDeleteOne is the builder for deleting a single City entity. +type CityDeleteOne struct { + cd *CityDelete +} + +// Exec executes the deletion query. +func (cdo *CityDeleteOne) Exec(ctx context.Context) error { + return cdo.cd.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (cdo *CityDeleteOne) ExecX(ctx context.Context) { + cdo.cd.ExecX(ctx) +} diff --git a/examples/edgeindex/ent/city_query.go b/examples/edgeindex/ent/city_query.go new file mode 100644 index 000000000..8cae551c6 --- /dev/null +++ b/examples/edgeindex/ent/city_query.go @@ -0,0 +1,487 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "math" + + "github.com/facebookincubator/ent/examples/edgeindex/ent/city" + "github.com/facebookincubator/ent/examples/edgeindex/ent/predicate" + "github.com/facebookincubator/ent/examples/edgeindex/ent/street" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// CityQuery is the builder for querying City entities. +type CityQuery struct { + config + limit *int + offset *int + order []Order + unique []string + predicates []predicate.City + // intermediate queries. + sql *sql.Selector +} + +// Where adds a new predicate for the builder. +func (cq *CityQuery) Where(ps ...predicate.City) *CityQuery { + cq.predicates = append(cq.predicates, ps...) + return cq +} + +// Limit adds a limit step to the query. +func (cq *CityQuery) Limit(limit int) *CityQuery { + cq.limit = &limit + return cq +} + +// Offset adds an offset step to the query. +func (cq *CityQuery) Offset(offset int) *CityQuery { + cq.offset = &offset + return cq +} + +// Order adds an order step to the query. +func (cq *CityQuery) Order(o ...Order) *CityQuery { + cq.order = append(cq.order, o...) + return cq +} + +// QueryStreets chains the current query on the streets edge. +func (cq *CityQuery) QueryStreets() *StreetQuery { + query := &StreetQuery{config: cq.config} + t1 := sql.Table(street.Table) + t2 := cq.sqlQuery() + t2.Select(t2.C(city.FieldID)) + query.sql = sql.Select(). + From(t1). + Join(t2). + On(t1.C(city.StreetsColumn), t2.C(city.FieldID)) + return query +} + +// Get returns a City entity by its id. +func (cq *CityQuery) Get(ctx context.Context, id int) (*City, error) { + return cq.Where(city.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (cq *CityQuery) GetX(ctx context.Context, id int) *City { + c, err := cq.Get(ctx, id) + if err != nil { + panic(err) + } + return c +} + +// First returns the first City entity in the query. Returns *ErrNotFound when no city was found. +func (cq *CityQuery) First(ctx context.Context) (*City, error) { + cs, err := cq.Limit(1).All(ctx) + if err != nil { + return nil, err + } + if len(cs) == 0 { + return nil, &ErrNotFound{city.Label} + } + return cs[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (cq *CityQuery) FirstX(ctx context.Context) *City { + c, err := cq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return c +} + +// FirstID returns the first City id in the query. Returns *ErrNotFound when no id was found. +func (cq *CityQuery) FirstID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = cq.Limit(1).IDs(ctx); err != nil { + return + } + if len(ids) == 0 { + err = &ErrNotFound{city.Label} + return + } + return ids[0], nil +} + +// FirstXID is like FirstID, but panics if an error occurs. +func (cq *CityQuery) FirstXID(ctx context.Context) int { + id, err := cq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns the only City entity in the query, returns an error if not exactly one entity was returned. +func (cq *CityQuery) Only(ctx context.Context) (*City, error) { + cs, err := cq.Limit(2).All(ctx) + if err != nil { + return nil, err + } + switch len(cs) { + case 1: + return cs[0], nil + case 0: + return nil, &ErrNotFound{city.Label} + default: + return nil, &ErrNotSingular{city.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (cq *CityQuery) OnlyX(ctx context.Context) *City { + c, err := cq.Only(ctx) + if err != nil { + panic(err) + } + return c +} + +// OnlyID returns the only City id in the query, returns an error if not exactly one id was returned. +func (cq *CityQuery) OnlyID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = cq.Limit(2).IDs(ctx); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &ErrNotFound{city.Label} + default: + err = &ErrNotSingular{city.Label} + } + return +} + +// OnlyXID is like OnlyID, but panics if an error occurs. +func (cq *CityQuery) OnlyXID(ctx context.Context) int { + id, err := cq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Cities. +func (cq *CityQuery) All(ctx context.Context) ([]*City, error) { + return cq.sqlAll(ctx) +} + +// AllX is like All, but panics if an error occurs. +func (cq *CityQuery) AllX(ctx context.Context) []*City { + cs, err := cq.All(ctx) + if err != nil { + panic(err) + } + return cs +} + +// IDs executes the query and returns a list of City ids. +func (cq *CityQuery) IDs(ctx context.Context) ([]int, error) { + return cq.sqlIDs(ctx) +} + +// IDsX is like IDs, but panics if an error occurs. +func (cq *CityQuery) IDsX(ctx context.Context) []int { + ids, err := cq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (cq *CityQuery) Count(ctx context.Context) (int, error) { + return cq.sqlCount(ctx) +} + +// CountX is like Count, but panics if an error occurs. +func (cq *CityQuery) CountX(ctx context.Context) int { + count, err := cq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (cq *CityQuery) Exist(ctx context.Context) (bool, error) { + return cq.sqlExist(ctx) +} + +// ExistX is like Exist, but panics if an error occurs. +func (cq *CityQuery) ExistX(ctx context.Context) bool { + exist, err := cq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the query builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (cq *CityQuery) Clone() *CityQuery { + return &CityQuery{ + config: cq.config, + limit: cq.limit, + offset: cq.offset, + order: append([]Order{}, cq.order...), + unique: append([]string{}, cq.unique...), + predicates: append([]predicate.City{}, cq.predicates...), + // clone intermediate queries. + sql: cq.sql.Clone(), + } +} + +// GroupBy used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// Name string `json:"name,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.City.Query(). +// GroupBy(city.FieldName). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +// +func (cq *CityQuery) GroupBy(field string, fields ...string) *CityGroupBy { + group := &CityGroupBy{config: cq.config} + group.fields = append([]string{field}, fields...) + group.sql = cq.sqlQuery() + return group +} + +func (cq *CityQuery) sqlAll(ctx context.Context) ([]*City, error) { + rows := &sql.Rows{} + selector := cq.sqlQuery() + if unique := cq.unique; len(unique) == 0 { + selector.Distinct() + } + query, args := selector.Query() + if err := cq.driver.Query(ctx, query, args, rows); err != nil { + return nil, err + } + defer rows.Close() + var cs Cities + if err := cs.FromRows(rows); err != nil { + return nil, err + } + cs.config(cq.config) + return cs, nil +} + +func (cq *CityQuery) sqlCount(ctx context.Context) (int, error) { + rows := &sql.Rows{} + selector := cq.sqlQuery() + unique := []string{city.FieldID} + if len(cq.unique) > 0 { + unique = cq.unique + } + selector.Count(sql.Distinct(selector.Columns(unique...)...)) + query, args := selector.Query() + if err := cq.driver.Query(ctx, query, args, rows); err != nil { + return 0, err + } + defer rows.Close() + if !rows.Next() { + return 0, errors.New("ent: no rows found") + } + var n int + if err := rows.Scan(&n); err != nil { + return 0, fmt.Errorf("ent: failed reading count: %v", err) + } + return n, nil +} + +func (cq *CityQuery) sqlExist(ctx context.Context) (bool, error) { + n, err := cq.sqlCount(ctx) + if err != nil { + return false, fmt.Errorf("ent: check existence: %v", err) + } + return n > 0, nil +} + +func (cq *CityQuery) sqlIDs(ctx context.Context) ([]int, error) { + vs, err := cq.sqlAll(ctx) + if err != nil { + return nil, err + } + var ids []int + for _, v := range vs { + ids = append(ids, v.ID) + } + return ids, nil +} + +func (cq *CityQuery) sqlQuery() *sql.Selector { + t1 := sql.Table(city.Table) + selector := sql.Select(t1.Columns(city.Columns...)...).From(t1) + if cq.sql != nil { + selector = cq.sql + selector.Select(selector.Columns(city.Columns...)...) + } + for _, p := range cq.predicates { + p(selector) + } + for _, p := range cq.order { + p(selector) + } + if offset := cq.offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt64) + } + if limit := cq.limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// CityQuery is the builder for group-by City entities. +type CityGroupBy struct { + config + fields []string + fns []Aggregate + // intermediate queries. + sql *sql.Selector +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (cgb *CityGroupBy) Aggregate(fns ...Aggregate) *CityGroupBy { + cgb.fns = append(cgb.fns, fns...) + return cgb +} + +// Scan applies the group-by query and scan the result into the given value. +func (cgb *CityGroupBy) Scan(ctx context.Context, v interface{}) error { + return cgb.sqlScan(ctx, v) +} + +// ScanX is like Scan, but panics if an error occurs. +func (cgb *CityGroupBy) ScanX(ctx context.Context, v interface{}) { + if err := cgb.Scan(ctx, v); err != nil { + panic(err) + } +} + +// Strings returns list of strings from group-by. It is only allowed when querying group-by with one field. +func (cgb *CityGroupBy) Strings(ctx context.Context) ([]string, error) { + if len(cgb.fields) > 1 { + return nil, errors.New("ent: CityGroupBy.Strings is not achievable when grouping more than 1 field") + } + var v []string + if err := cgb.Scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// StringsX is like Strings, but panics if an error occurs. +func (cgb *CityGroupBy) StringsX(ctx context.Context) []string { + v, err := cgb.Strings(ctx) + if err != nil { + panic(err) + } + return v +} + +// Ints returns list of ints from group-by. It is only allowed when querying group-by with one field. +func (cgb *CityGroupBy) Ints(ctx context.Context) ([]int, error) { + if len(cgb.fields) > 1 { + return nil, errors.New("ent: CityGroupBy.Ints is not achievable when grouping more than 1 field") + } + var v []int + if err := cgb.Scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// IntsX is like Ints, but panics if an error occurs. +func (cgb *CityGroupBy) IntsX(ctx context.Context) []int { + v, err := cgb.Ints(ctx) + if err != nil { + panic(err) + } + return v +} + +// Float64s returns list of float64s from group-by. It is only allowed when querying group-by with one field. +func (cgb *CityGroupBy) Float64s(ctx context.Context) ([]float64, error) { + if len(cgb.fields) > 1 { + return nil, errors.New("ent: CityGroupBy.Float64s is not achievable when grouping more than 1 field") + } + var v []float64 + if err := cgb.Scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// Float64sX is like Float64s, but panics if an error occurs. +func (cgb *CityGroupBy) Float64sX(ctx context.Context) []float64 { + v, err := cgb.Float64s(ctx) + if err != nil { + panic(err) + } + return v +} + +// Bools returns list of bools from group-by. It is only allowed when querying group-by with one field. +func (cgb *CityGroupBy) Bools(ctx context.Context) ([]bool, error) { + if len(cgb.fields) > 1 { + return nil, errors.New("ent: CityGroupBy.Bools is not achievable when grouping more than 1 field") + } + var v []bool + if err := cgb.Scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// BoolsX is like Bools, but panics if an error occurs. +func (cgb *CityGroupBy) BoolsX(ctx context.Context) []bool { + v, err := cgb.Bools(ctx) + if err != nil { + panic(err) + } + return v +} + +func (cgb *CityGroupBy) sqlScan(ctx context.Context, v interface{}) error { + rows := &sql.Rows{} + query, args := cgb.sqlQuery().Query() + if err := cgb.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +func (cgb *CityGroupBy) sqlQuery() *sql.Selector { + selector := cgb.sql + columns := make([]string, 0, len(cgb.fields)+len(cgb.fns)) + columns = append(columns, cgb.fields...) + for _, fn := range cgb.fns { + columns = append(columns, fn.SQL(selector)) + } + return selector.Select(columns...).GroupBy(cgb.fields...) +} diff --git a/examples/edgeindex/ent/city_update.go b/examples/edgeindex/ent/city_update.go new file mode 100644 index 000000000..18f475217 --- /dev/null +++ b/examples/edgeindex/ent/city_update.go @@ -0,0 +1,360 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + + "github.com/facebookincubator/ent/examples/edgeindex/ent/city" + "github.com/facebookincubator/ent/examples/edgeindex/ent/predicate" + "github.com/facebookincubator/ent/examples/edgeindex/ent/street" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// CityUpdate is the builder for updating City entities. +type CityUpdate struct { + config + name *string + streets map[int]struct{} + removedStreets map[int]struct{} + predicates []predicate.City +} + +// Where adds a new predicate for the builder. +func (cu *CityUpdate) Where(ps ...predicate.City) *CityUpdate { + cu.predicates = append(cu.predicates, ps...) + return cu +} + +// SetName sets the name field. +func (cu *CityUpdate) SetName(s string) *CityUpdate { + cu.name = &s + return cu +} + +// AddStreetIDs adds the streets edge to Street by ids. +func (cu *CityUpdate) AddStreetIDs(ids ...int) *CityUpdate { + if cu.streets == nil { + cu.streets = make(map[int]struct{}) + } + for i := range ids { + cu.streets[ids[i]] = struct{}{} + } + return cu +} + +// AddStreets adds the streets edges to Street. +func (cu *CityUpdate) AddStreets(s ...*Street) *CityUpdate { + ids := make([]int, len(s)) + for i := range s { + ids[i] = s[i].ID + } + return cu.AddStreetIDs(ids...) +} + +// RemoveStreetIDs removes the streets edge to Street by ids. +func (cu *CityUpdate) RemoveStreetIDs(ids ...int) *CityUpdate { + if cu.removedStreets == nil { + cu.removedStreets = make(map[int]struct{}) + } + for i := range ids { + cu.removedStreets[ids[i]] = struct{}{} + } + return cu +} + +// RemoveStreets removes streets edges to Street. +func (cu *CityUpdate) RemoveStreets(s ...*Street) *CityUpdate { + ids := make([]int, len(s)) + for i := range s { + ids[i] = s[i].ID + } + return cu.RemoveStreetIDs(ids...) +} + +// Save executes the query and returns the number of rows/vertices matched by this operation. +func (cu *CityUpdate) Save(ctx context.Context) (int, error) { + return cu.sqlSave(ctx) +} + +// SaveX is like Save, but panics if an error occurs. +func (cu *CityUpdate) SaveX(ctx context.Context) int { + affected, err := cu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (cu *CityUpdate) Exec(ctx context.Context) error { + _, err := cu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (cu *CityUpdate) ExecX(ctx context.Context) { + if err := cu.Exec(ctx); err != nil { + panic(err) + } +} + +func (cu *CityUpdate) sqlSave(ctx context.Context) (n int, err error) { + selector := sql.Select(city.FieldID).From(sql.Table(city.Table)) + for _, p := range cu.predicates { + p(selector) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err = cu.driver.Query(ctx, query, args, rows); err != nil { + return 0, err + } + defer rows.Close() + var ids []int + for rows.Next() { + var id int + if err := rows.Scan(&id); err != nil { + return 0, fmt.Errorf("ent: failed reading id: %v", err) + } + ids = append(ids, id) + } + if len(ids) == 0 { + return 0, nil + } + + tx, err := cu.driver.Tx(ctx) + if err != nil { + return 0, err + } + var ( + update bool + res sql.Result + builder = sql.Update(city.Table).Where(sql.InInts(city.FieldID, ids...)) + ) + if cu.name != nil { + update = true + builder.Set(city.FieldName, *cu.name) + } + if update { + query, args := builder.Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return 0, rollback(tx, err) + } + } + if len(cu.removedStreets) > 0 { + eids := make([]int, len(cu.removedStreets)) + for eid := range cu.removedStreets { + eids = append(eids, eid) + } + query, args := sql.Update(city.StreetsTable). + SetNull(city.StreetsColumn). + Where(sql.InInts(city.StreetsColumn, ids...)). + Where(sql.InInts(street.FieldID, eids...)). + Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return 0, rollback(tx, err) + } + } + if len(cu.streets) > 0 { + for _, id := range ids { + p := sql.P() + for eid := range cu.streets { + p.Or().EQ(street.FieldID, eid) + } + query, args := sql.Update(city.StreetsTable). + Set(city.StreetsColumn, id). + Where(sql.And(p, sql.IsNull(city.StreetsColumn))). + Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return 0, rollback(tx, err) + } + affected, err := res.RowsAffected() + if err != nil { + return 0, rollback(tx, err) + } + if int(affected) < len(cu.streets) { + return 0, rollback(tx, &ErrConstraintFailed{msg: fmt.Sprintf("one of \"streets\" %v already connected to a different \"City\"", keys(cu.streets))}) + } + } + } + if err = tx.Commit(); err != nil { + return 0, err + } + return len(ids), nil +} + +// CityUpdateOne is the builder for updating a single City entity. +type CityUpdateOne struct { + config + id int + name *string + streets map[int]struct{} + removedStreets map[int]struct{} +} + +// SetName sets the name field. +func (cuo *CityUpdateOne) SetName(s string) *CityUpdateOne { + cuo.name = &s + return cuo +} + +// AddStreetIDs adds the streets edge to Street by ids. +func (cuo *CityUpdateOne) AddStreetIDs(ids ...int) *CityUpdateOne { + if cuo.streets == nil { + cuo.streets = make(map[int]struct{}) + } + for i := range ids { + cuo.streets[ids[i]] = struct{}{} + } + return cuo +} + +// AddStreets adds the streets edges to Street. +func (cuo *CityUpdateOne) AddStreets(s ...*Street) *CityUpdateOne { + ids := make([]int, len(s)) + for i := range s { + ids[i] = s[i].ID + } + return cuo.AddStreetIDs(ids...) +} + +// RemoveStreetIDs removes the streets edge to Street by ids. +func (cuo *CityUpdateOne) RemoveStreetIDs(ids ...int) *CityUpdateOne { + if cuo.removedStreets == nil { + cuo.removedStreets = make(map[int]struct{}) + } + for i := range ids { + cuo.removedStreets[ids[i]] = struct{}{} + } + return cuo +} + +// RemoveStreets removes streets edges to Street. +func (cuo *CityUpdateOne) RemoveStreets(s ...*Street) *CityUpdateOne { + ids := make([]int, len(s)) + for i := range s { + ids[i] = s[i].ID + } + return cuo.RemoveStreetIDs(ids...) +} + +// Save executes the query and returns the updated entity. +func (cuo *CityUpdateOne) Save(ctx context.Context) (*City, error) { + return cuo.sqlSave(ctx) +} + +// SaveX is like Save, but panics if an error occurs. +func (cuo *CityUpdateOne) SaveX(ctx context.Context) *City { + c, err := cuo.Save(ctx) + if err != nil { + panic(err) + } + return c +} + +// Exec executes the query on the entity. +func (cuo *CityUpdateOne) Exec(ctx context.Context) error { + _, err := cuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (cuo *CityUpdateOne) ExecX(ctx context.Context) { + if err := cuo.Exec(ctx); err != nil { + panic(err) + } +} + +func (cuo *CityUpdateOne) sqlSave(ctx context.Context) (c *City, err error) { + selector := sql.Select(city.Columns...).From(sql.Table(city.Table)) + city.ID(cuo.id)(selector) + rows := &sql.Rows{} + query, args := selector.Query() + if err = cuo.driver.Query(ctx, query, args, rows); err != nil { + return nil, err + } + defer rows.Close() + var ids []int + for rows.Next() { + var id int + c = &City{config: cuo.config} + if err := c.FromRows(rows); err != nil { + return nil, fmt.Errorf("ent: failed scanning row into City: %v", err) + } + id = c.ID + ids = append(ids, id) + } + switch n := len(ids); { + case n == 0: + return nil, fmt.Errorf("ent: City not found with id: %v", cuo.id) + case n > 1: + return nil, fmt.Errorf("ent: more than one City with the same id: %v", cuo.id) + } + + tx, err := cuo.driver.Tx(ctx) + if err != nil { + return nil, err + } + var ( + update bool + res sql.Result + builder = sql.Update(city.Table).Where(sql.InInts(city.FieldID, ids...)) + ) + if cuo.name != nil { + update = true + builder.Set(city.FieldName, *cuo.name) + c.Name = *cuo.name + } + if update { + query, args := builder.Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return nil, rollback(tx, err) + } + } + if len(cuo.removedStreets) > 0 { + eids := make([]int, len(cuo.removedStreets)) + for eid := range cuo.removedStreets { + eids = append(eids, eid) + } + query, args := sql.Update(city.StreetsTable). + SetNull(city.StreetsColumn). + Where(sql.InInts(city.StreetsColumn, ids...)). + Where(sql.InInts(street.FieldID, eids...)). + Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return nil, rollback(tx, err) + } + } + if len(cuo.streets) > 0 { + for _, id := range ids { + p := sql.P() + for eid := range cuo.streets { + p.Or().EQ(street.FieldID, eid) + } + query, args := sql.Update(city.StreetsTable). + Set(city.StreetsColumn, id). + Where(sql.And(p, sql.IsNull(city.StreetsColumn))). + Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return nil, rollback(tx, err) + } + affected, err := res.RowsAffected() + if err != nil { + return nil, rollback(tx, err) + } + if int(affected) < len(cuo.streets) { + return nil, rollback(tx, &ErrConstraintFailed{msg: fmt.Sprintf("one of \"streets\" %v already connected to a different \"City\"", keys(cuo.streets))}) + } + } + } + if err = tx.Commit(); err != nil { + return nil, err + } + return c, nil +} diff --git a/examples/edgeindex/ent/client.go b/examples/edgeindex/ent/client.go new file mode 100644 index 000000000..3e5c78998 --- /dev/null +++ b/examples/edgeindex/ent/client.go @@ -0,0 +1,183 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "log" + + "github.com/facebookincubator/ent/examples/edgeindex/ent/migrate" + + "github.com/facebookincubator/ent/examples/edgeindex/ent/city" + "github.com/facebookincubator/ent/examples/edgeindex/ent/street" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// Client is the client that holds all ent builders. +type Client struct { + config + // Schema is the client for creating, migrating and dropping schema. + Schema *migrate.Schema + // City is the client for interacting with the City builders. + City *CityClient + // Street is the client for interacting with the Street builders. + Street *StreetClient +} + +// NewClient creates a new client configured with the given options. +func NewClient(opts ...Option) *Client { + c := config{log: log.Println} + c.options(opts...) + return &Client{ + config: c, + Schema: migrate.NewSchema(c.driver), + City: NewCityClient(c), + Street: NewStreetClient(c), + } +} + +// Tx returns a new transactional client. +func (c *Client) Tx(ctx context.Context) (*Tx, error) { + if _, ok := c.driver.(*txDriver); ok { + return nil, fmt.Errorf("ent: cannot start a transaction within a transaction") + } + tx, err := newTx(ctx, c.driver) + if err != nil { + return nil, fmt.Errorf("ent: starting a transaction: %v", err) + } + cfg := config{driver: tx, log: c.log, verbose: c.verbose} + return &Tx{ + config: cfg, + City: NewCityClient(cfg), + Street: NewStreetClient(cfg), + }, nil +} + +// CityClient is a client for the City schema. +type CityClient struct { + config +} + +// NewCityClient returns a client for the City from the given config. +func NewCityClient(c config) *CityClient { + return &CityClient{config: c} +} + +// Create returns a create builder for City. +func (c *CityClient) Create() *CityCreate { + return &CityCreate{config: c.config} +} + +// Update returns an update builder for City. +func (c *CityClient) Update() *CityUpdate { + return &CityUpdate{config: c.config} +} + +// UpdateOne returns an update builder for the given entity. +func (c *CityClient) UpdateOne(ci *City) *CityUpdateOne { + return c.UpdateOneID(ci.ID) +} + +// UpdateOneID returns an update builder for the given id. +func (c *CityClient) UpdateOneID(id int) *CityUpdateOne { + return &CityUpdateOne{config: c.config, id: id} +} + +// Delete returns a delete builder for City. +func (c *CityClient) Delete() *CityDelete { + return &CityDelete{config: c.config} +} + +// DeleteOne returns a delete builder for the given entity. +func (c *CityClient) DeleteOne(ci *City) *CityDeleteOne { + return c.DeleteOneID(ci.ID) +} + +// DeleteOneID returns a delete builder for the given id. +func (c *CityClient) DeleteOneID(id int) *CityDeleteOne { + return &CityDeleteOne{c.Delete().Where(city.ID(id))} +} + +// Create returns a query builder for City. +func (c *CityClient) Query() *CityQuery { + return &CityQuery{config: c.config} +} + +// QueryStreets queries the streets edge of a City. +func (c *CityClient) QueryStreets(ci *City) *StreetQuery { + query := &StreetQuery{config: c.config} + id := ci.ID + query.sql = sql.Select().From(sql.Table(street.Table)). + Where(sql.EQ(city.StreetsColumn, id)) + + return query +} + +// StreetClient is a client for the Street schema. +type StreetClient struct { + config +} + +// NewStreetClient returns a client for the Street from the given config. +func NewStreetClient(c config) *StreetClient { + return &StreetClient{config: c} +} + +// Create returns a create builder for Street. +func (c *StreetClient) Create() *StreetCreate { + return &StreetCreate{config: c.config} +} + +// Update returns an update builder for Street. +func (c *StreetClient) Update() *StreetUpdate { + return &StreetUpdate{config: c.config} +} + +// UpdateOne returns an update builder for the given entity. +func (c *StreetClient) UpdateOne(s *Street) *StreetUpdateOne { + return c.UpdateOneID(s.ID) +} + +// UpdateOneID returns an update builder for the given id. +func (c *StreetClient) UpdateOneID(id int) *StreetUpdateOne { + return &StreetUpdateOne{config: c.config, id: id} +} + +// Delete returns a delete builder for Street. +func (c *StreetClient) Delete() *StreetDelete { + return &StreetDelete{config: c.config} +} + +// DeleteOne returns a delete builder for the given entity. +func (c *StreetClient) DeleteOne(s *Street) *StreetDeleteOne { + return c.DeleteOneID(s.ID) +} + +// DeleteOneID returns a delete builder for the given id. +func (c *StreetClient) DeleteOneID(id int) *StreetDeleteOne { + return &StreetDeleteOne{c.Delete().Where(street.ID(id))} +} + +// Create returns a query builder for Street. +func (c *StreetClient) Query() *StreetQuery { + return &StreetQuery{config: c.config} +} + +// QueryCity queries the city edge of a Street. +func (c *StreetClient) QueryCity(s *Street) *CityQuery { + query := &CityQuery{config: c.config} + id := s.ID + t1 := sql.Table(city.Table) + t2 := sql.Select(street.CityColumn). + From(sql.Table(street.CityTable)). + Where(sql.EQ(street.FieldID, id)) + query.sql = sql.Select().From(t1).Join(t2).On(t1.C(city.FieldID), t2.C(street.CityColumn)) + + return query +} diff --git a/examples/edgeindex/ent/config.go b/examples/edgeindex/ent/config.go new file mode 100644 index 000000000..dbe938d37 --- /dev/null +++ b/examples/edgeindex/ent/config.go @@ -0,0 +1,55 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "github.com/facebookincubator/ent/dialect" +) + +// Option function to configure the client. +type Option func(*config) + +// Config is the configuration for the client and its builder. +type config struct { + // driver is the driver used for execute database requests. + driver dialect.Driver + // verbose enable a verbosity logging. + verbose bool + // log used for logging on verbose mode. + log func(...interface{}) +} + +// Options applies the options on the config object. +func (c *config) options(opts ...Option) { + for _, opt := range opts { + opt(c) + } + if c.verbose { + c.driver = dialect.Debug(c.driver, c.log) + } +} + +// Verbose sets the client logging to verbose. +func Verbose() Option { + return func(c *config) { + c.verbose = true + } +} + +// Log sets the client logging to verbose. +func Log(fn func(...interface{})) Option { + return func(c *config) { + c.log = fn + } +} + +// Driver configures the client driver. +func Driver(driver dialect.Driver) Option { + return func(c *config) { + c.driver = driver + } +} diff --git a/examples/edgeindex/ent/context.go b/examples/edgeindex/ent/context.go new file mode 100644 index 000000000..f57bccf74 --- /dev/null +++ b/examples/edgeindex/ent/context.go @@ -0,0 +1,24 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "context" +) + +type contextKey struct{} + +// FromContext returns the Client stored in a context, or nil if there isn't one. +func FromContext(ctx context.Context) *Client { + c, _ := ctx.Value(contextKey{}).(*Client) + return c +} + +// NewContext returns a new context with the given Client attached. +func NewContext(parent context.Context, c *Client) context.Context { + return context.WithValue(parent, contextKey{}, c) +} diff --git a/examples/edgeindex/ent/ent.go b/examples/edgeindex/ent/ent.go new file mode 100644 index 000000000..8e8d0e8d3 --- /dev/null +++ b/examples/edgeindex/ent/ent.go @@ -0,0 +1,196 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "github.com/facebookincubator/ent/dialect" + "github.com/facebookincubator/ent/dialect/sql" +) + +// Order applies an ordering on either graph traversal or sql selector. +type Order func(*sql.Selector) + +// Asc applies the given fields in ASC order. +func Asc(fields ...string) Order { + return Order( + func(s *sql.Selector) { + for _, f := range fields { + s.OrderBy(sql.Asc(f)) + } + }, + ) +} + +// Desc applies the given fields in DESC order. +func Desc(fields ...string) Order { + return Order( + func(s *sql.Selector) { + for _, f := range fields { + s.OrderBy(sql.Desc(f)) + } + }, + ) +} + +// Aggregate applies an aggregation step on the group-by traversal/selector. +type Aggregate struct { + // SQL the column wrapped with the aggregation function. + SQL func(*sql.Selector) string +} + +// As is a pseudo aggregation function for renaming another other functions with custom names. For example: +// +// GroupBy(field1, field2). +// Aggregate(ent.As(ent.Sum(field1), "sum_field1"), (ent.As(ent.Sum(field2), "sum_field2")). +// Scan(ctx, &v) +// +func As(fn Aggregate, end string) Aggregate { + return Aggregate{ + SQL: func(s *sql.Selector) string { + return sql.As(fn.SQL(s), end) + }, + } +} + +// Count applies the "count" aggregation function on each group. +func Count() Aggregate { + return Aggregate{ + SQL: func(s *sql.Selector) string { + return sql.Count("*") + }, + } +} + +// Max applies the "max" aggregation function on the given field of each group. +func Max(field string) Aggregate { + return Aggregate{ + SQL: func(s *sql.Selector) string { + return sql.Max(s.C(field)) + }, + } +} + +// Mean applies the "mean" aggregation function on the given field of each group. +func Mean(field string) Aggregate { + return Aggregate{ + SQL: func(s *sql.Selector) string { + return sql.Avg(s.C(field)) + }, + } +} + +// Min applies the "min" aggregation function on the given field of each group. +func Min(field string) Aggregate { + return Aggregate{ + SQL: func(s *sql.Selector) string { + return sql.Min(s.C(field)) + }, + } +} + +// Sum applies the "sum" aggregation function on the given field of each group. +func Sum(field string) Aggregate { + return Aggregate{ + SQL: func(s *sql.Selector) string { + return sql.Sum(s.C(field)) + }, + } +} + +// ErrNotFound returns when trying to fetch a specific entity and it was not found in the database. +type ErrNotFound struct { + label string +} + +// Error implements the error interface. +func (e *ErrNotFound) Error() string { + return fmt.Sprintf("ent: %s not found", e.label) +} + +// IsNotFound returns a boolean indicating whether the error is a not found error. +func IsNotFound(err error) bool { + _, ok := err.(*ErrNotFound) + return ok +} + +// MaskNotFound masks nor found error. +func MaskNotFound(err error) error { + if IsNotFound(err) { + return nil + } + return err +} + +// ErrNotSingular returns when trying to fetch a singular entity and more then one was found in the database. +type ErrNotSingular struct { + label string +} + +// Error implements the error interface. +func (e *ErrNotSingular) Error() string { + return fmt.Sprintf("ent: %s not singular", e.label) +} + +// IsNotSingular returns a boolean indicating whether the error is a not singular error. +func IsNotSingular(err error) bool { + _, ok := err.(*ErrNotSingular) + return ok +} + +// ErrConstraintFailed returns when trying to create/update one or more entities and +// one or more of their constraints failed. For example, violation of edge or field uniqueness. +type ErrConstraintFailed struct { + msg string + wrap error +} + +// Error implements the error interface. +func (e ErrConstraintFailed) Error() string { + return fmt.Sprintf("ent: unique constraint failed: %s", e.msg) +} + +// Unwrap implements the errors.Wrapper interface. +func (e *ErrConstraintFailed) Unwrap() error { + return e.wrap +} + +// IsConstraintFailure returns a boolean indicating whether the error is a constraint failure. +func IsConstraintFailure(err error) bool { + _, ok := err.(*ErrConstraintFailed) + return ok +} + +func isSQLConstraintError(err error) (*ErrConstraintFailed, bool) { + // Error number 1062 is ER_DUP_ENTRY in mysql, and "UNIQUE constraint failed" is SQLite prefix. + if msg := err.Error(); strings.HasPrefix(msg, "Error 1062") || strings.HasPrefix(msg, "UNIQUE constraint failed") { + return &ErrConstraintFailed{msg, err}, true + } + return nil, false +} + +// rollback calls to tx.Rollback and wraps the given error with the rollback error if occurred. +func rollback(tx dialect.Tx, err error) error { + if rerr := tx.Rollback(); rerr != nil { + err = fmt.Errorf("%s: %v", err.Error(), rerr) + } + if err, ok := isSQLConstraintError(err); ok { + return err + } + return err +} + +// keys returns the keys/ids from the edge map. +func keys(m map[int]struct{}) []int { + s := make([]int, 0, len(m)) + for id, _ := range m { + s = append(s, id) + } + return s +} diff --git a/examples/edgeindex/ent/example_test.go b/examples/edgeindex/ent/example_test.go new file mode 100644 index 000000000..3cd9b60b4 --- /dev/null +++ b/examples/edgeindex/ent/example_test.go @@ -0,0 +1,80 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "context" + "log" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// dsn for the database. In order to run the tests locally, run the following command: +// +// ENT_INTEGRATION_ENDPOINT="root:pass@tcp(localhost:3306)/test?parseTime=True" go test -v +// +var dsn string + +func ExampleCity() { + if dsn == "" { + return + } + ctx := context.Background() + drv, err := sql.Open("mysql", dsn) + if err != nil { + log.Fatalf("failed creating database client: %v", err) + } + defer drv.Close() + client := NewClient(Driver(drv)) + // creating vertices for the city's edges. + s0 := client.Street. + Create(). + SetName("string"). + SaveX(ctx) + log.Println("street created:", s0) + + // create city vertex with its edges. + c := client.City. + Create(). + SetName("string"). + AddStreets(s0). + SaveX(ctx) + log.Println("city created:", c) + + // query edges. + s0, err = c.QueryStreets().First(ctx) + if err != nil { + log.Fatalf("failed querying streets: %v", err) + } + log.Println("streets found:", s0) + + // Output: +} +func ExampleStreet() { + if dsn == "" { + return + } + ctx := context.Background() + drv, err := sql.Open("mysql", dsn) + if err != nil { + log.Fatalf("failed creating database client: %v", err) + } + defer drv.Close() + client := NewClient(Driver(drv)) + // creating vertices for the street's edges. + + // create street vertex with its edges. + s := client.Street. + Create(). + SetName("string"). + SaveX(ctx) + log.Println("street created:", s) + + // query edges. + + // Output: +} diff --git a/examples/edgeindex/ent/generate.go b/examples/edgeindex/ent/generate.go new file mode 100644 index 000000000..b38816c3a --- /dev/null +++ b/examples/edgeindex/ent/generate.go @@ -0,0 +1,7 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +package ent + +//go:generate go run ../../../entc/cmd/entc/entc.go generate --header "Code generated (@generated) by entc, DO NOT EDIT." ./schema diff --git a/examples/edgeindex/ent/migrate/migrate.go b/examples/edgeindex/ent/migrate/migrate.go new file mode 100644 index 000000000..a237d2dfb --- /dev/null +++ b/examples/edgeindex/ent/migrate/migrate.go @@ -0,0 +1,52 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package migrate + +import ( + "context" + "fmt" + + "github.com/facebookincubator/ent/dialect" + "github.com/facebookincubator/ent/dialect/sql/schema" +) + +var ( + // WithGlobalUniqueID sets the universal ids options to the migration. + // If this option is enabled, ent migration will allocate a 1<<32 range + // for the ids of each entity (table). + // Note that this option cannot be applied on tables that already exist. + WithGlobalUniqueID = schema.WithGlobalUniqueID + // WithDropColumn sets the drop column option to the migration. + // If this option is enabled, ent migration will drop old columns + // that were used for both fields and edges. This defaults to false. + WithDropColumn = schema.WithDropColumn + // WithDropIndex sets the drop index option to the migration. + // If this option is enabled, ent migration will drop old indexes + // that were defined in the schema. This defaults to false. + // Note that unique constraints are defined using `UNIQUE INDEX`, + // and therefore, it's recommended to enable this option to get more + // flexibility in the schema changes. + WithDropIndex = schema.WithDropIndex +) + +// Schema is the API for creating, migrating and dropping a schema. +type Schema struct { + drv dialect.Driver + universalID bool +} + +// NewSchema creates a new schema client. +func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } + +// Create creates all schema resources. +func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { + migrate, err := schema.NewMigrate(s.drv, opts...) + if err != nil { + return fmt.Errorf("ent/migrate: %v", err) + } + return migrate.Create(ctx, Tables...) +} diff --git a/examples/edgeindex/ent/migrate/schema.go b/examples/edgeindex/ent/migrate/schema.go new file mode 100644 index 000000000..de4d540bf --- /dev/null +++ b/examples/edgeindex/ent/migrate/schema.go @@ -0,0 +1,64 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package migrate + +import ( + "github.com/facebookincubator/ent/dialect/sql/schema" + "github.com/facebookincubator/ent/schema/field" +) + +var ( + // CitiesColumns holds the columns for the "cities" table. + CitiesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + {Name: "name", Type: field.TypeString}, + } + // CitiesTable holds the schema information for the "cities" table. + CitiesTable = &schema.Table{ + Name: "cities", + Columns: CitiesColumns, + PrimaryKey: []*schema.Column{CitiesColumns[0]}, + ForeignKeys: []*schema.ForeignKey{}, + } + // StreetsColumns holds the columns for the "streets" table. + StreetsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + {Name: "name", Type: field.TypeString}, + {Name: "city_id", Type: field.TypeInt, Nullable: true}, + } + // StreetsTable holds the schema information for the "streets" table. + StreetsTable = &schema.Table{ + Name: "streets", + Columns: StreetsColumns, + PrimaryKey: []*schema.Column{StreetsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "streets_cities_streets", + Columns: []*schema.Column{StreetsColumns[2]}, + + RefColumns: []*schema.Column{CitiesColumns[0]}, + OnDelete: schema.SetNull, + }, + }, + Indexes: []*schema.Index{ + { + Name: "name_city_id", + Unique: true, + Columns: []*schema.Column{StreetsColumns[1], StreetsColumns[2]}, + }, + }, + } + // Tables holds all the tables in the schema. + Tables = []*schema.Table{ + CitiesTable, + StreetsTable, + } +) + +func init() { + StreetsTable.ForeignKeys[0].RefTable = CitiesTable +} diff --git a/examples/edgeindex/ent/predicate/predicate.go b/examples/edgeindex/ent/predicate/predicate.go new file mode 100644 index 000000000..62c1b966f --- /dev/null +++ b/examples/edgeindex/ent/predicate/predicate.go @@ -0,0 +1,17 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package predicate + +import ( + "github.com/facebookincubator/ent/dialect/sql" +) + +// City is the predicate function for city builders. +type City func(*sql.Selector) + +// Street is the predicate function for street builders. +type Street func(*sql.Selector) diff --git a/examples/edgeindex/ent/schema/city.go b/examples/edgeindex/ent/schema/city.go new file mode 100644 index 000000000..392bb7be1 --- /dev/null +++ b/examples/edgeindex/ent/schema/city.go @@ -0,0 +1,30 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +package schema + +import ( + "github.com/facebookincubator/ent" + "github.com/facebookincubator/ent/schema/edge" + "github.com/facebookincubator/ent/schema/field" +) + +// City holds the schema definition for the City entity. +type City struct { + ent.Schema +} + +// Fields of the City. +func (City) Fields() []ent.Field { + return []ent.Field{ + field.String("name"), + } +} + +// Edges of the City. +func (City) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("streets", Street.Type), + } +} diff --git a/examples/edgeindex/ent/schema/street.go b/examples/edgeindex/ent/schema/street.go new file mode 100644 index 000000000..6544db418 --- /dev/null +++ b/examples/edgeindex/ent/schema/street.go @@ -0,0 +1,42 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +package schema + +import ( + "github.com/facebookincubator/ent" + "github.com/facebookincubator/ent/schema/edge" + "github.com/facebookincubator/ent/schema/field" + "github.com/facebookincubator/ent/schema/index" +) + +// Street holds the schema definition for the Street entity. +type Street struct { + ent.Schema +} + +// Fields of the Street. +func (Street) Fields() []ent.Field { + return []ent.Field{ + field.String("name"), + } +} + +// Edges of the Street. +func (Street) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("city", City.Type). + Ref("streets"). + Unique(), + } +} + +// Indexes of the Street. +func (Street) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("name"). + Edges("city"). + Unique(), + } +} diff --git a/examples/edgeindex/ent/street.go b/examples/edgeindex/ent/street.go new file mode 100644 index 000000000..7b77b557b --- /dev/null +++ b/examples/edgeindex/ent/street.go @@ -0,0 +1,95 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "bytes" + "fmt" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// Street is the model entity for the Street schema. +type Street struct { + config + // ID of the ent. + ID int `json:"id,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` +} + +// FromRows scans the sql response data into Street. +func (s *Street) FromRows(rows *sql.Rows) error { + var vs struct { + ID int + Name sql.NullString + } + // the order here should be the same as in the `street.Columns`. + if err := rows.Scan( + &vs.ID, + &vs.Name, + ); err != nil { + return err + } + s.ID = vs.ID + s.Name = vs.Name.String + return nil +} + +// QueryCity queries the city edge of the Street. +func (s *Street) QueryCity() *CityQuery { + return (&StreetClient{s.config}).QueryCity(s) +} + +// Update returns a builder for updating this Street. +// Note that, you need to call Street.Unwrap() before calling this method, if this Street +// was returned from a transaction, and the transaction was committed or rolled back. +func (s *Street) Update() *StreetUpdateOne { + return (&StreetClient{s.config}).UpdateOne(s) +} + +// Unwrap unwraps the entity that was returned from a transaction after it was closed, +// so that all next queries will be executed through the driver which created the transaction. +func (s *Street) Unwrap() *Street { + tx, ok := s.config.driver.(*txDriver) + if !ok { + panic("ent: Street is not a transactional entity") + } + s.config.driver = tx.drv + return s +} + +// String implements the fmt.Stringer. +func (s *Street) String() string { + buf := bytes.NewBuffer(nil) + buf.WriteString("Street(") + buf.WriteString(fmt.Sprintf("id=%v", s.ID)) + buf.WriteString(fmt.Sprintf(", name=%v", s.Name)) + buf.WriteString(")") + return buf.String() +} + +// Streets is a parsable slice of Street. +type Streets []*Street + +// FromRows scans the sql response data into Streets. +func (s *Streets) FromRows(rows *sql.Rows) error { + for rows.Next() { + vs := &Street{} + if err := vs.FromRows(rows); err != nil { + return err + } + *s = append(*s, vs) + } + return nil +} + +func (s Streets) config(cfg config) { + for i := range s { + s[i].config = cfg + } +} diff --git a/examples/edgeindex/ent/street/street.go b/examples/edgeindex/ent/street/street.go new file mode 100644 index 000000000..665f74100 --- /dev/null +++ b/examples/edgeindex/ent/street/street.go @@ -0,0 +1,32 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package street + +const ( + // Label holds the string label denoting the street type in the database. + Label = "street" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldName holds the string denoting the name vertex property in the database. + FieldName = "name" + + // Table holds the table name of the street in the database. + Table = "streets" + // CityTable is the table the holds the city relation/edge. + CityTable = "streets" + // CityInverseTable is the table name for the City entity. + // It exists in this package in order to avoid circular dependency with the "city" package. + CityInverseTable = "cities" + // CityColumn is the table column denoting the city relation/edge. + CityColumn = "city_id" +) + +// Columns holds all SQL columns are street fields. +var Columns = []string{ + FieldID, + FieldName, +} diff --git a/examples/edgeindex/ent/street/where.go b/examples/edgeindex/ent/street/where.go new file mode 100644 index 000000000..3a48eae17 --- /dev/null +++ b/examples/edgeindex/ent/street/where.go @@ -0,0 +1,309 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package street + +import ( + "github.com/facebookincubator/ent/examples/edgeindex/ent/predicate" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// ID filters vertices based on their identifier. +func ID(id int) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldID), id)) + }, + ) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldID), id)) + }, + ) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldID), id)) + }, + ) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.GT(s.C(FieldID), id)) + }, + ) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.GTE(s.C(FieldID), id)) + }, + ) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.LT(s.C(FieldID), id)) + }, + ) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.LTE(s.C(FieldID), id)) + }, + ) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + // if not arguments were provided, append the FALSE constants, + // since we can't apply "IN ()". This will make this predicate falsy. + if len(ids) == 0 { + s.Where(sql.False()) + return + } + v := make([]interface{}, len(ids)) + for i := range v { + v[i] = ids[i] + } + s.Where(sql.In(s.C(FieldID), v...)) + }, + ) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + // if not arguments were provided, append the FALSE constants, + // since we can't apply "IN ()". This will make this predicate falsy. + if len(ids) == 0 { + s.Where(sql.False()) + return + } + v := make([]interface{}, len(ids)) + for i := range v { + v[i] = ids[i] + } + s.Where(sql.NotIn(s.C(FieldID), v...)) + }, + ) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldName), v)) + }, + ) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldName), v)) + }, + ) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldName), v)) + }, + ) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.GT(s.C(FieldName), v)) + }, + ) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.GTE(s.C(FieldName), v)) + }, + ) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.LT(s.C(FieldName), v)) + }, + ) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.LTE(s.C(FieldName), v)) + }, + ) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.Street { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.Street( + func(s *sql.Selector) { + // if not arguments were provided, append the FALSE constants, + // since we can't apply "IN ()". This will make this predicate falsy. + if len(vs) == 0 { + s.Where(sql.False()) + return + } + s.Where(sql.In(s.C(FieldName), v...)) + }, + ) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.Street { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.Street( + func(s *sql.Selector) { + // if not arguments were provided, append the FALSE constants, + // since we can't apply "IN ()". This will make this predicate falsy. + if len(vs) == 0 { + s.Where(sql.False()) + return + } + s.Where(sql.NotIn(s.C(FieldName), v...)) + }, + ) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.Contains(s.C(FieldName), v)) + }, + ) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.HasPrefix(s.C(FieldName), v)) + }, + ) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.HasSuffix(s.C(FieldName), v)) + }, + ) +} + +// NameContainsFold applies the ContainsFold predicate on the "name" field. +func NameContainsFold(v string) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + s.Where(sql.ContainsFold(s.C(FieldName), v)) + }, + ) +} + +// HasCity applies the HasEdge predicate on the "city" edge. +func HasCity() predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + t1 := s.Table() + s.Where(sql.NotNull(t1.C(CityColumn))) + }, + ) +} + +// HasCityWith applies the HasEdge predicate on the "city" edge with a given conditions (other predicates). +func HasCityWith(preds ...predicate.City) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + t1 := s.Table() + t2 := sql.Select(FieldID).From(sql.Table(CityInverseTable)) + for _, p := range preds { + p(t2) + } + s.Where(sql.In(t1.C(CityColumn), t2)) + }, + ) +} + +// And groups list of predicates with the AND operator between them. +func And(predicates ...predicate.Street) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + for _, p := range predicates { + p(s) + } + }, + ) +} + +// Or groups list of predicates with the OR operator between them. +func Or(predicates ...predicate.Street) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + for i, p := range predicates { + if i > 0 { + s.Or() + } + p(s) + } + }, + ) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.Street) predicate.Street { + return predicate.Street( + func(s *sql.Selector) { + p(s.Not()) + }, + ) +} diff --git a/examples/edgeindex/ent/street_create.go b/examples/edgeindex/ent/street_create.go new file mode 100644 index 000000000..2f3248021 --- /dev/null +++ b/examples/edgeindex/ent/street_create.go @@ -0,0 +1,111 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + + "github.com/facebookincubator/ent/examples/edgeindex/ent/street" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// StreetCreate is the builder for creating a Street entity. +type StreetCreate struct { + config + name *string + city map[int]struct{} +} + +// SetName sets the name field. +func (sc *StreetCreate) SetName(s string) *StreetCreate { + sc.name = &s + return sc +} + +// SetCityID sets the city edge to City by id. +func (sc *StreetCreate) SetCityID(id int) *StreetCreate { + if sc.city == nil { + sc.city = make(map[int]struct{}) + } + sc.city[id] = struct{}{} + return sc +} + +// SetNillableCityID sets the city edge to City by id if the given value is not nil. +func (sc *StreetCreate) SetNillableCityID(id *int) *StreetCreate { + if id != nil { + sc = sc.SetCityID(*id) + } + return sc +} + +// SetCity sets the city edge to City. +func (sc *StreetCreate) SetCity(c *City) *StreetCreate { + return sc.SetCityID(c.ID) +} + +// Save creates the Street in the database. +func (sc *StreetCreate) Save(ctx context.Context) (*Street, error) { + if sc.name == nil { + return nil, errors.New("ent: missing required field \"name\"") + } + if len(sc.city) > 1 { + return nil, errors.New("ent: multiple assignments on a unique edge \"city\"") + } + return sc.sqlSave(ctx) +} + +// SaveX calls Save and panics if Save returns an error. +func (sc *StreetCreate) SaveX(ctx context.Context) *Street { + v, err := sc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +func (sc *StreetCreate) sqlSave(ctx context.Context) (*Street, error) { + var ( + res sql.Result + s = &Street{config: sc.config} + ) + tx, err := sc.driver.Tx(ctx) + if err != nil { + return nil, err + } + builder := sql.Insert(street.Table).Default(sc.driver.Dialect()) + if sc.name != nil { + builder.Set(street.FieldName, *sc.name) + s.Name = *sc.name + } + query, args := builder.Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return nil, rollback(tx, err) + } + id, err := res.LastInsertId() + if err != nil { + return nil, rollback(tx, err) + } + s.ID = int(id) + if len(sc.city) > 0 { + for eid := range sc.city { + query, args := sql.Update(street.CityTable). + Set(street.CityColumn, eid). + Where(sql.EQ(street.FieldID, id)). + Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return nil, rollback(tx, err) + } + } + } + if err := tx.Commit(); err != nil { + return nil, err + } + return s, nil +} diff --git a/examples/edgeindex/ent/street_delete.go b/examples/edgeindex/ent/street_delete.go new file mode 100644 index 000000000..0b8bc3307 --- /dev/null +++ b/examples/edgeindex/ent/street_delete.go @@ -0,0 +1,65 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "context" + + "github.com/facebookincubator/ent/examples/edgeindex/ent/predicate" + "github.com/facebookincubator/ent/examples/edgeindex/ent/street" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// StreetDelete is the builder for deleting a Street entity. +type StreetDelete struct { + config + predicates []predicate.Street +} + +// Where adds a new predicate for the builder. +func (sd *StreetDelete) Where(ps ...predicate.Street) *StreetDelete { + sd.predicates = append(sd.predicates, ps...) + return sd +} + +// Exec executes the deletion query. +func (sd *StreetDelete) Exec(ctx context.Context) error { + return sd.sqlExec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (sd *StreetDelete) ExecX(ctx context.Context) { + if err := sd.Exec(ctx); err != nil { + panic(err) + } +} + +func (sd *StreetDelete) sqlExec(ctx context.Context) error { + var res sql.Result + selector := sql.Select().From(sql.Table(street.Table)) + for _, p := range sd.predicates { + p(selector) + } + query, args := sql.Delete(street.Table).FromSelect(selector).Query() + return sd.driver.Exec(ctx, query, args, &res) +} + +// StreetDeleteOne is the builder for deleting a single Street entity. +type StreetDeleteOne struct { + sd *StreetDelete +} + +// Exec executes the deletion query. +func (sdo *StreetDeleteOne) Exec(ctx context.Context) error { + return sdo.sd.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (sdo *StreetDeleteOne) ExecX(ctx context.Context) { + sdo.sd.ExecX(ctx) +} diff --git a/examples/edgeindex/ent/street_query.go b/examples/edgeindex/ent/street_query.go new file mode 100644 index 000000000..9535f9ce8 --- /dev/null +++ b/examples/edgeindex/ent/street_query.go @@ -0,0 +1,487 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "math" + + "github.com/facebookincubator/ent/examples/edgeindex/ent/city" + "github.com/facebookincubator/ent/examples/edgeindex/ent/predicate" + "github.com/facebookincubator/ent/examples/edgeindex/ent/street" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// StreetQuery is the builder for querying Street entities. +type StreetQuery struct { + config + limit *int + offset *int + order []Order + unique []string + predicates []predicate.Street + // intermediate queries. + sql *sql.Selector +} + +// Where adds a new predicate for the builder. +func (sq *StreetQuery) Where(ps ...predicate.Street) *StreetQuery { + sq.predicates = append(sq.predicates, ps...) + return sq +} + +// Limit adds a limit step to the query. +func (sq *StreetQuery) Limit(limit int) *StreetQuery { + sq.limit = &limit + return sq +} + +// Offset adds an offset step to the query. +func (sq *StreetQuery) Offset(offset int) *StreetQuery { + sq.offset = &offset + return sq +} + +// Order adds an order step to the query. +func (sq *StreetQuery) Order(o ...Order) *StreetQuery { + sq.order = append(sq.order, o...) + return sq +} + +// QueryCity chains the current query on the city edge. +func (sq *StreetQuery) QueryCity() *CityQuery { + query := &CityQuery{config: sq.config} + t1 := sql.Table(city.Table) + t2 := sq.sqlQuery() + t2.Select(t2.C(street.CityColumn)) + query.sql = sql.Select(t1.Columns(city.Columns...)...). + From(t1). + Join(t2). + On(t1.C(city.FieldID), t2.C(street.CityColumn)) + return query +} + +// Get returns a Street entity by its id. +func (sq *StreetQuery) Get(ctx context.Context, id int) (*Street, error) { + return sq.Where(street.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (sq *StreetQuery) GetX(ctx context.Context, id int) *Street { + s, err := sq.Get(ctx, id) + if err != nil { + panic(err) + } + return s +} + +// First returns the first Street entity in the query. Returns *ErrNotFound when no street was found. +func (sq *StreetQuery) First(ctx context.Context) (*Street, error) { + sSlice, err := sq.Limit(1).All(ctx) + if err != nil { + return nil, err + } + if len(sSlice) == 0 { + return nil, &ErrNotFound{street.Label} + } + return sSlice[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (sq *StreetQuery) FirstX(ctx context.Context) *Street { + s, err := sq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return s +} + +// FirstID returns the first Street id in the query. Returns *ErrNotFound when no id was found. +func (sq *StreetQuery) FirstID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = sq.Limit(1).IDs(ctx); err != nil { + return + } + if len(ids) == 0 { + err = &ErrNotFound{street.Label} + return + } + return ids[0], nil +} + +// FirstXID is like FirstID, but panics if an error occurs. +func (sq *StreetQuery) FirstXID(ctx context.Context) int { + id, err := sq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns the only Street entity in the query, returns an error if not exactly one entity was returned. +func (sq *StreetQuery) Only(ctx context.Context) (*Street, error) { + sSlice, err := sq.Limit(2).All(ctx) + if err != nil { + return nil, err + } + switch len(sSlice) { + case 1: + return sSlice[0], nil + case 0: + return nil, &ErrNotFound{street.Label} + default: + return nil, &ErrNotSingular{street.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (sq *StreetQuery) OnlyX(ctx context.Context) *Street { + s, err := sq.Only(ctx) + if err != nil { + panic(err) + } + return s +} + +// OnlyID returns the only Street id in the query, returns an error if not exactly one id was returned. +func (sq *StreetQuery) OnlyID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = sq.Limit(2).IDs(ctx); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &ErrNotFound{street.Label} + default: + err = &ErrNotSingular{street.Label} + } + return +} + +// OnlyXID is like OnlyID, but panics if an error occurs. +func (sq *StreetQuery) OnlyXID(ctx context.Context) int { + id, err := sq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Streets. +func (sq *StreetQuery) All(ctx context.Context) ([]*Street, error) { + return sq.sqlAll(ctx) +} + +// AllX is like All, but panics if an error occurs. +func (sq *StreetQuery) AllX(ctx context.Context) []*Street { + sSlice, err := sq.All(ctx) + if err != nil { + panic(err) + } + return sSlice +} + +// IDs executes the query and returns a list of Street ids. +func (sq *StreetQuery) IDs(ctx context.Context) ([]int, error) { + return sq.sqlIDs(ctx) +} + +// IDsX is like IDs, but panics if an error occurs. +func (sq *StreetQuery) IDsX(ctx context.Context) []int { + ids, err := sq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (sq *StreetQuery) Count(ctx context.Context) (int, error) { + return sq.sqlCount(ctx) +} + +// CountX is like Count, but panics if an error occurs. +func (sq *StreetQuery) CountX(ctx context.Context) int { + count, err := sq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (sq *StreetQuery) Exist(ctx context.Context) (bool, error) { + return sq.sqlExist(ctx) +} + +// ExistX is like Exist, but panics if an error occurs. +func (sq *StreetQuery) ExistX(ctx context.Context) bool { + exist, err := sq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the query builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (sq *StreetQuery) Clone() *StreetQuery { + return &StreetQuery{ + config: sq.config, + limit: sq.limit, + offset: sq.offset, + order: append([]Order{}, sq.order...), + unique: append([]string{}, sq.unique...), + predicates: append([]predicate.Street{}, sq.predicates...), + // clone intermediate queries. + sql: sq.sql.Clone(), + } +} + +// GroupBy used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// Name string `json:"name,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.Street.Query(). +// GroupBy(street.FieldName). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +// +func (sq *StreetQuery) GroupBy(field string, fields ...string) *StreetGroupBy { + group := &StreetGroupBy{config: sq.config} + group.fields = append([]string{field}, fields...) + group.sql = sq.sqlQuery() + return group +} + +func (sq *StreetQuery) sqlAll(ctx context.Context) ([]*Street, error) { + rows := &sql.Rows{} + selector := sq.sqlQuery() + if unique := sq.unique; len(unique) == 0 { + selector.Distinct() + } + query, args := selector.Query() + if err := sq.driver.Query(ctx, query, args, rows); err != nil { + return nil, err + } + defer rows.Close() + var sSlice Streets + if err := sSlice.FromRows(rows); err != nil { + return nil, err + } + sSlice.config(sq.config) + return sSlice, nil +} + +func (sq *StreetQuery) sqlCount(ctx context.Context) (int, error) { + rows := &sql.Rows{} + selector := sq.sqlQuery() + unique := []string{street.FieldID} + if len(sq.unique) > 0 { + unique = sq.unique + } + selector.Count(sql.Distinct(selector.Columns(unique...)...)) + query, args := selector.Query() + if err := sq.driver.Query(ctx, query, args, rows); err != nil { + return 0, err + } + defer rows.Close() + if !rows.Next() { + return 0, errors.New("ent: no rows found") + } + var n int + if err := rows.Scan(&n); err != nil { + return 0, fmt.Errorf("ent: failed reading count: %v", err) + } + return n, nil +} + +func (sq *StreetQuery) sqlExist(ctx context.Context) (bool, error) { + n, err := sq.sqlCount(ctx) + if err != nil { + return false, fmt.Errorf("ent: check existence: %v", err) + } + return n > 0, nil +} + +func (sq *StreetQuery) sqlIDs(ctx context.Context) ([]int, error) { + vs, err := sq.sqlAll(ctx) + if err != nil { + return nil, err + } + var ids []int + for _, v := range vs { + ids = append(ids, v.ID) + } + return ids, nil +} + +func (sq *StreetQuery) sqlQuery() *sql.Selector { + t1 := sql.Table(street.Table) + selector := sql.Select(t1.Columns(street.Columns...)...).From(t1) + if sq.sql != nil { + selector = sq.sql + selector.Select(selector.Columns(street.Columns...)...) + } + for _, p := range sq.predicates { + p(selector) + } + for _, p := range sq.order { + p(selector) + } + if offset := sq.offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt64) + } + if limit := sq.limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// StreetQuery is the builder for group-by Street entities. +type StreetGroupBy struct { + config + fields []string + fns []Aggregate + // intermediate queries. + sql *sql.Selector +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (sgb *StreetGroupBy) Aggregate(fns ...Aggregate) *StreetGroupBy { + sgb.fns = append(sgb.fns, fns...) + return sgb +} + +// Scan applies the group-by query and scan the result into the given value. +func (sgb *StreetGroupBy) Scan(ctx context.Context, v interface{}) error { + return sgb.sqlScan(ctx, v) +} + +// ScanX is like Scan, but panics if an error occurs. +func (sgb *StreetGroupBy) ScanX(ctx context.Context, v interface{}) { + if err := sgb.Scan(ctx, v); err != nil { + panic(err) + } +} + +// Strings returns list of strings from group-by. It is only allowed when querying group-by with one field. +func (sgb *StreetGroupBy) Strings(ctx context.Context) ([]string, error) { + if len(sgb.fields) > 1 { + return nil, errors.New("ent: StreetGroupBy.Strings is not achievable when grouping more than 1 field") + } + var v []string + if err := sgb.Scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// StringsX is like Strings, but panics if an error occurs. +func (sgb *StreetGroupBy) StringsX(ctx context.Context) []string { + v, err := sgb.Strings(ctx) + if err != nil { + panic(err) + } + return v +} + +// Ints returns list of ints from group-by. It is only allowed when querying group-by with one field. +func (sgb *StreetGroupBy) Ints(ctx context.Context) ([]int, error) { + if len(sgb.fields) > 1 { + return nil, errors.New("ent: StreetGroupBy.Ints is not achievable when grouping more than 1 field") + } + var v []int + if err := sgb.Scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// IntsX is like Ints, but panics if an error occurs. +func (sgb *StreetGroupBy) IntsX(ctx context.Context) []int { + v, err := sgb.Ints(ctx) + if err != nil { + panic(err) + } + return v +} + +// Float64s returns list of float64s from group-by. It is only allowed when querying group-by with one field. +func (sgb *StreetGroupBy) Float64s(ctx context.Context) ([]float64, error) { + if len(sgb.fields) > 1 { + return nil, errors.New("ent: StreetGroupBy.Float64s is not achievable when grouping more than 1 field") + } + var v []float64 + if err := sgb.Scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// Float64sX is like Float64s, but panics if an error occurs. +func (sgb *StreetGroupBy) Float64sX(ctx context.Context) []float64 { + v, err := sgb.Float64s(ctx) + if err != nil { + panic(err) + } + return v +} + +// Bools returns list of bools from group-by. It is only allowed when querying group-by with one field. +func (sgb *StreetGroupBy) Bools(ctx context.Context) ([]bool, error) { + if len(sgb.fields) > 1 { + return nil, errors.New("ent: StreetGroupBy.Bools is not achievable when grouping more than 1 field") + } + var v []bool + if err := sgb.Scan(ctx, &v); err != nil { + return nil, err + } + return v, nil +} + +// BoolsX is like Bools, but panics if an error occurs. +func (sgb *StreetGroupBy) BoolsX(ctx context.Context) []bool { + v, err := sgb.Bools(ctx) + if err != nil { + panic(err) + } + return v +} + +func (sgb *StreetGroupBy) sqlScan(ctx context.Context, v interface{}) error { + rows := &sql.Rows{} + query, args := sgb.sqlQuery().Query() + if err := sgb.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +func (sgb *StreetGroupBy) sqlQuery() *sql.Selector { + selector := sgb.sql + columns := make([]string, 0, len(sgb.fields)+len(sgb.fns)) + columns = append(columns, sgb.fields...) + for _, fn := range sgb.fns { + columns = append(columns, fn.SQL(selector)) + } + return selector.Select(columns...).GroupBy(sgb.fields...) +} diff --git a/examples/edgeindex/ent/street_update.go b/examples/edgeindex/ent/street_update.go new file mode 100644 index 000000000..977967513 --- /dev/null +++ b/examples/edgeindex/ent/street_update.go @@ -0,0 +1,311 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "github.com/facebookincubator/ent/examples/edgeindex/ent/city" + "github.com/facebookincubator/ent/examples/edgeindex/ent/predicate" + "github.com/facebookincubator/ent/examples/edgeindex/ent/street" + + "github.com/facebookincubator/ent/dialect/sql" +) + +// StreetUpdate is the builder for updating Street entities. +type StreetUpdate struct { + config + name *string + city map[int]struct{} + clearedCity bool + predicates []predicate.Street +} + +// Where adds a new predicate for the builder. +func (su *StreetUpdate) Where(ps ...predicate.Street) *StreetUpdate { + su.predicates = append(su.predicates, ps...) + return su +} + +// SetName sets the name field. +func (su *StreetUpdate) SetName(s string) *StreetUpdate { + su.name = &s + return su +} + +// SetCityID sets the city edge to City by id. +func (su *StreetUpdate) SetCityID(id int) *StreetUpdate { + if su.city == nil { + su.city = make(map[int]struct{}) + } + su.city[id] = struct{}{} + return su +} + +// SetNillableCityID sets the city edge to City by id if the given value is not nil. +func (su *StreetUpdate) SetNillableCityID(id *int) *StreetUpdate { + if id != nil { + su = su.SetCityID(*id) + } + return su +} + +// SetCity sets the city edge to City. +func (su *StreetUpdate) SetCity(c *City) *StreetUpdate { + return su.SetCityID(c.ID) +} + +// ClearCity clears the city edge to City. +func (su *StreetUpdate) ClearCity() *StreetUpdate { + su.clearedCity = true + return su +} + +// Save executes the query and returns the number of rows/vertices matched by this operation. +func (su *StreetUpdate) Save(ctx context.Context) (int, error) { + if len(su.city) > 1 { + return 0, errors.New("ent: multiple assignments on a unique edge \"city\"") + } + return su.sqlSave(ctx) +} + +// SaveX is like Save, but panics if an error occurs. +func (su *StreetUpdate) SaveX(ctx context.Context) int { + affected, err := su.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (su *StreetUpdate) Exec(ctx context.Context) error { + _, err := su.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (su *StreetUpdate) ExecX(ctx context.Context) { + if err := su.Exec(ctx); err != nil { + panic(err) + } +} + +func (su *StreetUpdate) sqlSave(ctx context.Context) (n int, err error) { + selector := sql.Select(street.FieldID).From(sql.Table(street.Table)) + for _, p := range su.predicates { + p(selector) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err = su.driver.Query(ctx, query, args, rows); err != nil { + return 0, err + } + defer rows.Close() + var ids []int + for rows.Next() { + var id int + if err := rows.Scan(&id); err != nil { + return 0, fmt.Errorf("ent: failed reading id: %v", err) + } + ids = append(ids, id) + } + if len(ids) == 0 { + return 0, nil + } + + tx, err := su.driver.Tx(ctx) + if err != nil { + return 0, err + } + var ( + update bool + res sql.Result + builder = sql.Update(street.Table).Where(sql.InInts(street.FieldID, ids...)) + ) + if su.name != nil { + update = true + builder.Set(street.FieldName, *su.name) + } + if update { + query, args := builder.Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return 0, rollback(tx, err) + } + } + if su.clearedCity { + query, args := sql.Update(street.CityTable). + SetNull(street.CityColumn). + Where(sql.InInts(city.FieldID, ids...)). + Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return 0, rollback(tx, err) + } + } + if len(su.city) > 0 { + for eid := range su.city { + query, args := sql.Update(street.CityTable). + Set(street.CityColumn, eid). + Where(sql.InInts(street.FieldID, ids...)). + Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return 0, rollback(tx, err) + } + } + } + if err = tx.Commit(); err != nil { + return 0, err + } + return len(ids), nil +} + +// StreetUpdateOne is the builder for updating a single Street entity. +type StreetUpdateOne struct { + config + id int + name *string + city map[int]struct{} + clearedCity bool +} + +// SetName sets the name field. +func (suo *StreetUpdateOne) SetName(s string) *StreetUpdateOne { + suo.name = &s + return suo +} + +// SetCityID sets the city edge to City by id. +func (suo *StreetUpdateOne) SetCityID(id int) *StreetUpdateOne { + if suo.city == nil { + suo.city = make(map[int]struct{}) + } + suo.city[id] = struct{}{} + return suo +} + +// SetNillableCityID sets the city edge to City by id if the given value is not nil. +func (suo *StreetUpdateOne) SetNillableCityID(id *int) *StreetUpdateOne { + if id != nil { + suo = suo.SetCityID(*id) + } + return suo +} + +// SetCity sets the city edge to City. +func (suo *StreetUpdateOne) SetCity(c *City) *StreetUpdateOne { + return suo.SetCityID(c.ID) +} + +// ClearCity clears the city edge to City. +func (suo *StreetUpdateOne) ClearCity() *StreetUpdateOne { + suo.clearedCity = true + return suo +} + +// Save executes the query and returns the updated entity. +func (suo *StreetUpdateOne) Save(ctx context.Context) (*Street, error) { + if len(suo.city) > 1 { + return nil, errors.New("ent: multiple assignments on a unique edge \"city\"") + } + return suo.sqlSave(ctx) +} + +// SaveX is like Save, but panics if an error occurs. +func (suo *StreetUpdateOne) SaveX(ctx context.Context) *Street { + s, err := suo.Save(ctx) + if err != nil { + panic(err) + } + return s +} + +// Exec executes the query on the entity. +func (suo *StreetUpdateOne) Exec(ctx context.Context) error { + _, err := suo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (suo *StreetUpdateOne) ExecX(ctx context.Context) { + if err := suo.Exec(ctx); err != nil { + panic(err) + } +} + +func (suo *StreetUpdateOne) sqlSave(ctx context.Context) (s *Street, err error) { + selector := sql.Select(street.Columns...).From(sql.Table(street.Table)) + street.ID(suo.id)(selector) + rows := &sql.Rows{} + query, args := selector.Query() + if err = suo.driver.Query(ctx, query, args, rows); err != nil { + return nil, err + } + defer rows.Close() + var ids []int + for rows.Next() { + var id int + s = &Street{config: suo.config} + if err := s.FromRows(rows); err != nil { + return nil, fmt.Errorf("ent: failed scanning row into Street: %v", err) + } + id = s.ID + ids = append(ids, id) + } + switch n := len(ids); { + case n == 0: + return nil, fmt.Errorf("ent: Street not found with id: %v", suo.id) + case n > 1: + return nil, fmt.Errorf("ent: more than one Street with the same id: %v", suo.id) + } + + tx, err := suo.driver.Tx(ctx) + if err != nil { + return nil, err + } + var ( + update bool + res sql.Result + builder = sql.Update(street.Table).Where(sql.InInts(street.FieldID, ids...)) + ) + if suo.name != nil { + update = true + builder.Set(street.FieldName, *suo.name) + s.Name = *suo.name + } + if update { + query, args := builder.Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return nil, rollback(tx, err) + } + } + if suo.clearedCity { + query, args := sql.Update(street.CityTable). + SetNull(street.CityColumn). + Where(sql.InInts(city.FieldID, ids...)). + Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return nil, rollback(tx, err) + } + } + if len(suo.city) > 0 { + for eid := range suo.city { + query, args := sql.Update(street.CityTable). + Set(street.CityColumn, eid). + Where(sql.InInts(street.FieldID, ids...)). + Query() + if err := tx.Exec(ctx, query, args, &res); err != nil { + return nil, rollback(tx, err) + } + } + } + if err = tx.Commit(); err != nil { + return nil, err + } + return s, nil +} diff --git a/examples/edgeindex/ent/tx.go b/examples/edgeindex/ent/tx.go new file mode 100644 index 000000000..0c43d454d --- /dev/null +++ b/examples/edgeindex/ent/tx.go @@ -0,0 +1,100 @@ +// Copyright 2019-present Facebook Inc. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +// Code generated (@generated) by entc, DO NOT EDIT. + +package ent + +import ( + "context" + + "github.com/facebookincubator/ent/dialect" + "github.com/facebookincubator/ent/examples/edgeindex/ent/migrate" +) + +// Tx is a transactional client that is created by calling Client.Tx(). +type Tx struct { + config + // City is the client for interacting with the City builders. + City *CityClient + // Street is the client for interacting with the Street builders. + Street *StreetClient +} + +// Commit commits the transaction. +func (tx *Tx) Commit() error { + return tx.config.driver.(*txDriver).tx.Commit() +} + +// Rollback rollbacks the transaction. +func (tx *Tx) Rollback() error { + return tx.config.driver.(*txDriver).tx.Rollback() +} + +// Client returns a Client that binds to current transaction. +func (tx *Tx) Client() *Client { + return &Client{ + config: tx.config, + Schema: migrate.NewSchema(tx.driver), + City: NewCityClient(tx.config), + Street: NewStreetClient(tx.config), + } +} + +// txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. +// The idea is to support transactions without adding any extra code to the builders. +// When a builder calls to driver.Tx(), it gets the same dialect.Tx instance. +// Commit and Rollback are nop for the internal builders and the user must call one +// of them in order to commit or rollback the transaction. +// +// If a closed transaction is embedded in one of the generated entities, and the entity +// applies a query, for example: City.QueryXXX(), the query will be executed +// through the driver which created this transaction. +// +// Note that txDriver is not goroutine safe. +type txDriver struct { + // the driver we started the transaction from. + drv dialect.Driver + // tx is the underlying transaction. + tx dialect.Tx +} + +// newTx creates a new transactional driver. +func newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) { + tx, err := drv.Tx(ctx) + if err != nil { + return nil, err + } + return &txDriver{tx: tx, drv: drv}, nil +} + +// Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls +// from the internal builders. Should be called only by the internal builders. +func (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil } + +// Dialect returns the dialect of the driver we started the transaction from. +func (tx *txDriver) Dialect() string { return tx.drv.Dialect() } + +// Close is a nop close. +func (*txDriver) Close() error { return nil } + +// Commit is a nop commit for the internal builders. +// User must call `Tx.Commit` in order to commit the transaction. +func (*txDriver) Commit() error { return nil } + +// Rollback is a nop rollback for the internal builders. +// User must call `Tx.Rollback` in order to rollback the transaction. +func (*txDriver) Rollback() error { return nil } + +// Exec calls tx.Exec. +func (tx *txDriver) Exec(ctx context.Context, query string, args, v interface{}) error { + return tx.tx.Exec(ctx, query, args, v) +} + +// Query calls tx.Query. +func (tx *txDriver) Query(ctx context.Context, query string, args, v interface{}) error { + return tx.tx.Query(ctx, query, args, v) +} + +var _ dialect.Driver = (*txDriver)(nil)