From c203f043cfc79bb683b7cb230da4b18109afeb31 Mon Sep 17 00:00:00 2001 From: Ariel Mashraki Date: Thu, 1 Aug 2019 07:42:14 -0700 Subject: [PATCH] ent: add blob tyoe Reviewed By: idoshveki Differential Revision: D16600425 fbshipit-source-id: 04c6fe39f9b3b628a1e79eb3063188f582d9e504 --- dialect/sql/schema/schema.go | 6 ++ entc/gen/bindata.go | 4 +- entc/gen/func.go | 23 +++- entc/gen/graph.go | 27 ----- entc/gen/template.go | 28 ++++- entc/gen/template/builder/setter.tmpl | 5 +- entc/gen/type.go | 30 ++---- entc/gen/type_test.go | 3 +- .../integration/migrate/entv2/example_test.go | 1 + .../migrate/entv2/migrate/schema.go | 1 + entc/integration/migrate/entv2/schema/user.go | 4 +- entc/integration/migrate/entv2/user.go | 14 ++- entc/integration/migrate/entv2/user/user.go | 13 +++ entc/integration/migrate/entv2/user/where.go | 101 ++++++++++++++++++ entc/integration/migrate/entv2/user_create.go | 21 +++- entc/integration/migrate/entv2/user_update.go | 31 +++++- entc/integration/migrate/migrate_test.go | 3 + field/field.go | 55 +++++++++- field/field_test.go | 8 ++ 19 files changed, 302 insertions(+), 76 deletions(-) diff --git a/dialect/sql/schema/schema.go b/dialect/sql/schema/schema.go index 2a9c156cb..a94ee6116 100644 --- a/dialect/sql/schema/schema.go +++ b/dialect/sql/schema/schema.go @@ -176,6 +176,8 @@ func (c *Column) MySQLType(version string) (t string) { t = "bigint" case field.TypeUint, field.TypeUint64: t = "bigint unsigned" + case field.TypeBytes: + t = "blob" case field.TypeString: size := c.Size if size == 0 { @@ -211,6 +213,8 @@ func (c *Column) SQLiteType() (t string) { t = "integer" case field.TypeInt64, field.TypeUint64: t = "bigint" + case field.TypeBytes: + t = "blob" case field.TypeString: size := c.Size if size == 0 { @@ -279,6 +283,8 @@ func (c *Column) ScanMySQL(rows *sql.Rows) error { c.Type = field.TypeFloat64 case "timestamp": c.Type = field.TypeTime + case "blob": + c.Type = field.TypeBytes case "varchar": c.Type = field.TypeString size, err := strconv.Atoi(parts[1]) diff --git a/entc/gen/bindata.go b/entc/gen/bindata.go index 06657a253..c1ff1d586 100644 --- a/entc/gen/bindata.go +++ b/entc/gen/bindata.go @@ -184,7 +184,7 @@ func templateBuilderQueryTmpl() (*asset, error) { return a, nil } -var _templateBuilderSetterTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x56\xcd\x6e\xdb\x3c\x10\x3c\xcb\x4f\xb1\x9f\xa0\xaf\x90\x8c\x84\x41\xaf\x01\x7c\x29\xdc\xa0\xbe\xa4\x87\xb4\x27\xc3\x28\x14\x73\xe5\xb2\x55\x28\x45\x94\x5c\x04\x2a\xdf\xbd\x58\x92\xfa\x31\x2d\xc7\x0e\x8a\xde\x24\x72\x38\xbb\xdc\x9d\x1d\xa9\x6d\x81\x63\x26\x24\x42\xa8\xb0\xae\xb1\x0a\x41\xeb\x59\xdb\x42\xf4\xd8\x88\x9c\x63\x05\xb7\x0b\x28\x53\xb5\x4d\x73\x88\xd8\xc3\xb6\x28\x91\x7d\x70\x3b\x0e\x58\xe1\x16\xc5\xde\x22\xfb\xe7\xfe\xb8\xd6\x33\x42\x55\xa9\xdc\x21\x44\xdf\xae\x20\xca\x08\x18\xb1\x3b\x81\x39\x57\xb4\x1f\x10\x4b\x49\xab\x79\xf1\x0b\x2b\x88\xcb\x4a\xc8\x3a\x83\xf0\x7f\xf6\x5e\x85\x10\x65\xec\xcb\x4b\x89\x49\x0f\xcd\x1a\xb9\x35\x69\x11\x0c\xc2\x07\xac\x43\x88\xbb\x1c\x33\x76\x9f\x3e\x39\xf0\xcd\x0d\xf4\x78\xad\x41\x61\xad\xa0\xfe\x8e\x76\xd1\xe0\x68\x39\xa3\x44\xd8\x2c\x30\xb0\xf8\xe0\x46\x5a\xc3\x7c\x5c\x0b\xad\x93\x31\x63\x6c\x13\xd7\xda\x31\x52\x9a\x06\xe3\x1d\x82\x76\x16\x04\x1e\x31\xb3\x47\x1e\xea\xaa\xd9\xd6\xa6\x16\x04\x5c\xc0\xbb\x8e\x73\x16\x04\x15\xd6\x4d\x25\xc1\x3b\x39\x0b\x6c\x1d\x44\x06\x45\x45\x1c\x9f\xcb\x5a\x14\xd2\x5d\xbe\xc9\xf3\xf4\x31\x47\x7a\xfe\x94\xaa\x25\x66\x69\x93\xd7\x96\x8e\x78\xa4\xb0\xfb\x77\x47\x35\xbc\x77\x3b\x27\x6a\xd9\x15\xf3\x80\xe0\x4c\x51\x29\x45\xda\xda\x89\x3d\x4a\xd8\xa7\x79\x83\x20\x14\xc8\xa2\x06\x29\x72\x36\x0b\xde\x52\x73\x2f\xf0\x50\xfb\xf9\x05\xc5\x0f\x44\x06\xfd\x81\xff\x16\x14\xde\xae\x4f\xb7\xc5\x85\x98\x77\x47\x12\x82\x52\x11\x4e\xb6\xc4\xf5\x04\x25\x77\x63\xe1\x9e\x3c\xed\xa3\xd5\xfe\x47\xbe\xc3\x41\xfa\x85\xd1\x7e\x98\x72\x4e\xc3\x67\x3b\x1b\x21\xfb\x2a\xc5\x73\x83\x76\x85\x30\x0b\x33\xa2\x0e\xe2\xe8\xcd\x79\xc1\xd5\x61\x3b\xfb\x06\x16\x65\x02\xb1\x12\x72\xd7\xe4\x69\x45\x9c\xa6\x3d\xbf\xdd\x40\x27\x10\xae\x96\xea\x74\xcc\x8e\x77\x9a\xb6\x7b\x41\x27\x92\x70\xb5\xf4\x72\x73\x8a\xe9\x68\xdc\x98\x14\x54\xd0\x41\x32\xd8\x4b\x06\xf9\x0e\xa1\x2e\xdc\x2a\xb5\xb3\xdf\x7a\x7c\x01\xc1\x6d\x92\x24\x9e\x71\xa2\xaa\x0f\xf8\xb6\x11\x1e\xb2\x8a\x8f\x6f\x6f\x82\x61\xae\xec\xb3\x02\xc6\x58\x1f\xc6\x9c\x66\xab\xe5\xeb\x82\x73\x7a\xf3\x95\x85\x47\x03\x3f\xd2\xe2\x65\x07\xe0\x29\xfd\x89\xf1\x53\x5a\xae\xbd\x44\x36\xca\x20\x5b\xa3\x56\x37\xef\x07\xf7\xba\x36\x42\xbd\x28\xcc\x5a\xf0\x0d\x2c\xa0\x63\x6c\x2d\xdd\xb5\xad\x89\xe3\xc9\x8a\x0a\x84\x31\x7d\xa3\x6f\xaa\xd3\xe9\x91\x9a\x08\xa0\xd6\x62\x73\x14\x24\xe8\x23\x39\x11\x9d\x37\xc1\x54\xf2\xd1\x25\x23\x1c\xfc\xf0\xc8\xf5\x56\x4b\x75\x91\xf1\x79\x9a\x3e\x76\xbf\x8e\xc8\x37\xc0\xcb\xd5\xfc\x4f\xbc\x71\x48\x2b\x16\xdc\x42\xcf\x2a\x95\xa4\x2a\xf8\x69\x53\x34\x9a\x9b\x6a\xe9\x68\x82\xe6\x82\xbf\xd5\x22\xcf\x7d\xf4\x47\x35\xeb\xbf\xfc\x22\x03\x7c\xa6\x53\x07\x15\xb1\x44\x0b\x08\xf7\xa1\x7b\x1d\x87\xc8\x5e\x71\x46\xbf\xdd\x13\xff\x0c\x67\x1d\xab\x6d\x7d\x53\x1a\x7b\xd2\xb4\x02\xfe\xfe\x67\x63\xc2\x08\xc7\x1e\x35\xf7\x62\xbe\xf2\x4f\x32\xe5\x10\xd3\x1d\xf4\x9b\xde\xe5\xc3\x56\xcb\x64\xc2\x1e\xc8\x0f\x6e\x9d\x5b\xad\x37\x9e\x14\xaf\x20\x47\xd9\x33\x24\x49\xe7\x4b\xc6\x4f\x42\x31\x7c\x93\xa8\xdf\xc2\xa2\xec\xfe\x02\xc2\x1f\xa3\xef\xcc\xc8\x8b\xec\xbe\xd6\x83\x25\x0d\x15\x33\xaa\x26\xcb\xe9\x40\x1b\x27\x6a\xda\x1e\x16\xd9\x6a\x79\x46\xc6\x7e\x11\x04\x57\x8c\xb1\xc4\x33\xad\xf1\x1f\xc0\xf0\xf4\x27\x00\x00\xff\xff\x6c\x56\x09\x33\x6c\x0b\x00\x00") +var _templateBuilderSetterTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x56\x4d\x6f\xdb\x38\x10\x3d\xcb\xbf\x62\x56\xd0\x2e\x24\x23\xa1\xb1\xd7\x00\xbe\x2c\xbc\x41\x7d\x49\x0f\x6e\x4f\x86\x51\xc8\xe6\xc8\x9d\x56\xa1\x1c\x51\x72\x11\xa8\xfa\xef\x05\x3f\x24\xca\xb4\xec\x38\x28\x7a\x93\xc8\xf9\xe2\xbc\x37\x8f\x6c\x1a\xe0\x98\x91\x40\x08\x25\x56\x15\x96\x21\xb4\xed\xa4\x69\x20\xda\xd6\x94\x73\x2c\xe1\x61\x0e\x87\x54\xee\xd2\x1c\x22\xb6\xda\x15\x07\x64\xff\xd9\x1d\x6b\x58\xe2\x0e\xe9\x68\x2c\xfb\xef\xde\xbd\x6d\x27\xca\xaa\x4c\xc5\x1e\x21\xfa\x72\x07\x51\xa6\x0c\x23\xf6\x48\x98\x73\xa9\xf6\x03\x15\xe5\x70\xea\x9e\xb1\x4f\xaf\x07\x64\xab\xaa\x24\xb1\xef\x8d\xb2\x5a\xec\x74\x41\x25\x89\x0a\xc2\x15\x56\x21\xc4\x5d\x75\x19\x7b\x4a\x9f\x31\xd1\xc6\xb3\x19\xf4\xf6\x6d\x0b\x12\x2b\x09\xd5\x57\x34\x8b\xda\x4e\x2d\x67\xaa\x04\x36\x09\xb4\x59\x7c\x72\x96\xb6\x85\xe9\xb0\x0b\x6d\x9b\x0c\x23\xc6\xa6\xe4\xb6\xb5\x11\x55\xb1\xda\xc6\x73\x82\x66\x12\x04\x5e\x60\x66\x5c\x56\x55\x59\xef\x2a\xdd\x05\x65\x38\x87\x7f\xba\x98\x93\x20\x28\xb1\xaa\x4b\x01\x9e\xe7\x24\xd0\x7d\x98\x4d\x21\x3d\x16\xc4\x61\x8f\x02\xcb\x4a\x35\x48\x50\x9e\xa7\xdb\x1c\xc1\x80\x28\x21\x2b\x4a\x90\x39\xed\x50\xc2\x16\x77\x69\x2d\x51\x9f\x5f\x50\x0e\xc7\x34\xaf\xd1\x19\x00\x49\xb5\x44\x1c\xa6\x33\xdb\x67\xca\x20\x15\x1c\x62\x51\x54\x0e\x09\x65\x9b\x40\x5c\x68\x70\x3e\x1e\x2a\x2a\x84\xed\x7a\x6d\x73\x47\x19\xfb\x90\xca\x05\x66\x69\x9d\x57\x06\x07\x7d\xf6\xae\xb8\xc7\x33\xf4\x9e\xec\xce\x05\x14\x3b\x18\x4f\x02\xbc\x01\xa7\x2a\x5e\x6d\xed\xe9\x88\xc2\x9e\x95\x24\xa8\xa3\x08\xca\xd9\x24\x78\x0f\xda\x5e\x62\x87\xfa\xf4\x06\xd8\x03\xca\xa0\x77\xf8\x6b\xae\x7b\xaf\xd7\xc7\x09\x61\x53\x4c\x3b\x97\x44\x99\xaa\x26\x5c\x24\x83\x61\x03\xa0\xe0\x76\x14\xed\x97\x37\x6f\x68\xe6\xed\x7f\xbe\x47\x37\x6e\x85\x9e\xb7\x30\xe5\x5c\x0d\xbc\xc1\x3c\x42\xf6\x59\xd0\x4b\x8d\x66\x45\xd9\xcc\xb5\x2c\x58\x13\x1b\x5e\xfb\x13\x97\xa7\x70\xf6\x00\x16\x87\x04\x62\x49\x62\x5f\xe7\x69\xa9\x62\x6a\x78\x7e\x5a\x11\x49\x20\x5c\x2e\xe4\xe5\x9c\x5d\xdc\xf1\xb0\xdd\x0f\x5a\x92\x84\xcb\x85\x57\x9b\x65\x4c\x17\xc6\x0e\x68\xa1\x1a\xea\x28\x83\x3d\x65\x90\xef\x11\xaa\xc2\xae\x6a\xa2\x77\x5b\xdb\x57\x20\x6e\x8a\xd4\x73\x30\x28\x54\xf6\x09\xdf\x27\x1e\xae\xaa\xf8\xfc\xf4\x3a\x19\xe6\xd2\x7c\x4b\x60\x8c\xf5\x69\xb4\x37\x5b\x2e\xae\x13\xce\xf2\xcd\x67\x16\x9e\x49\xcd\x80\x8b\xb7\x39\xc0\x73\xfa\x1d\xe3\xe7\xf4\xb0\xf6\x0a\xd9\x48\x6d\xd9\x68\xb6\xda\x79\x3f\x39\xd7\xbd\x26\xea\x4d\x69\xd6\xc4\x37\x30\x87\x2e\x62\x63\xc2\xdd\x9b\x9e\xd8\x38\x4a\xb5\x48\xdf\x14\x9a\xdf\xaa\x4f\x97\x47\x6a\x24\x81\x5c\xd3\xe6\x2c\x49\xd0\x67\xb2\x24\xba\x2e\xbf\x9d\x3c\xba\x43\x46\xe8\x04\xf1\x4c\xf5\x96\x0b\x79\x93\xf0\x79\x9c\x3e\x57\xbf\x2e\x90\x2f\x80\xb7\xb3\xf9\x8f\x68\xa3\x2b\x2b\x56\x77\xc8\x4d\x4c\x55\x54\x25\x7e\x59\x14\x35\xe7\xc6\x20\x1d\x4c\xd0\x94\xf8\x7b\x25\xd2\x3d\x34\xf2\xe2\x07\x96\x10\x6b\x40\x32\x08\xff\x66\xff\xca\xf0\xa4\x67\x09\xb8\xbb\x10\x5f\x94\xd7\x49\x47\x4c\xa0\x39\x84\xc7\xd0\xfe\x0e\x53\x64\x57\x94\xd1\x87\x7b\xe4\xb5\xf2\xa6\x62\x35\x8d\x2f\x4a\x43\x4d\x1a\x67\xc0\xef\x3f\x73\x46\x84\x70\xa8\x51\x53\x2f\xe7\x95\xd7\xd0\x98\x42\x8c\x23\xe8\x83\xde\xd5\xc3\x96\x8b\x64\x44\x1e\x94\x1e\x3c\x58\xb5\x5a\x6f\x3c\x2a\xde\x41\x8e\xa2\x8f\x90\x24\x9d\x2e\x69\x3d\x09\xc9\xdd\x49\x0a\x6f\x32\x56\x66\x7f\x0e\xe1\xb7\xc1\x3d\x33\xd0\x22\xb3\xdf\xb6\x4e\x92\x5c\xc7\x34\xab\x95\xe4\x74\x46\x1b\x4b\x6a\xb5\xed\x16\xd9\x72\xf1\x06\x8d\xfd\x26\x10\x97\x8c\xb1\xc4\x13\xad\xe1\x0b\xc0\x7d\xfd\x0a\x00\x00\xff\xff\x44\xd3\xdd\xc8\xe0\x0b\x00\x00") func templateBuilderSetterTmplBytes() ([]byte, error) { return bindataRead( @@ -199,7 +199,7 @@ func templateBuilderSetterTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "template/builder/setter.tmpl", size: 2924, mode: os.FileMode(420), modTime: time.Unix(1564665941, 0)} + info := bindataFileInfo{name: "template/builder/setter.tmpl", size: 3040, mode: os.FileMode(420), modTime: time.Unix(1564666719, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/entc/gen/func.go b/entc/gen/func.go index 6a37c64ac..7098cbb93 100644 --- a/entc/gen/func.go +++ b/entc/gen/func.go @@ -117,15 +117,30 @@ func snake(s string) string { // receiver returns the receiver name of the given type. // +// []T => t // User => u // UserQuery => uq // func receiver(s string) (r string) { - words := strings.Split(snake(s), "_") - for _, w := range words { - r += w[:1] + // trim optional operators. + s = strings.Trim(s, "[]*&") + parts := strings.Split(snake(s), "_") + min := len(parts[0]) + for _, w := range parts[1:] { + if len(w) < min { + min = len(w) + } } - return + for i := 1; i < min; i++ { + r := parts[0][:i] + for _, w := range parts[1:] { + r += w[:i] + } + if _, ok := imports[r]; !ok { + return r + } + } + return strings.ToLower(s) } // scope wraps the Type object with extended context. diff --git a/entc/gen/graph.go b/entc/gen/graph.go index e4dcc0610..be35b9c7c 100644 --- a/entc/gen/graph.go +++ b/entc/gen/graph.go @@ -3,14 +3,11 @@ package gen import ( "bytes" "fmt" - "go/parser" - "go/token" "io" "io/ioutil" "os" "os/exec" "path/filepath" - "strconv" "fbc/ent/dialect/sql/schema" "fbc/ent/entc/load" @@ -30,8 +27,6 @@ type ( Header string // Storage to support in codegen. Storage []*Storage - // imports are the import packages used for code generation. - imports map[string]string } // Graph holds the nodes/entities of the loaded graph schema. Note that, it doesn't // hold the edges of the graph. Instead, each Type holds the edges for other Types. @@ -48,7 +43,6 @@ type ( // It fails if one of the schemas is invalid. func NewGraph(c Config, schemas ...*load.Schema) (g *Graph, err error) { defer catch(&err) - c.imports = imports() g = &Graph{c, make([]*Type, 0, len(schemas)), schemas} for _, schema := range schemas { g.addNode(schema) @@ -348,27 +342,6 @@ func (g *Graph) typ(name string) (*Type, bool) { return nil, false } -func imports() map[string]string { - var ( - specs = make(map[string]string) - b = bytes.NewBuffer([]byte("package main\n")) - ) - check(templates.ExecuteTemplate(b, "import", Type{}), "load imports") - f, err := parser.ParseFile(token.NewFileSet(), "", b, parser.ImportsOnly) - check(err, "parse imports") - for _, spec := range f.Imports { - path, err := strconv.Unquote(spec.Path.Value) - check(err, "unquote import path") - specs[filepath.Base(path)] = path - } - for _, s := range drivers { - for _, path := range s.Imports { - specs[filepath.Base(path)] = path - } - } - return specs -} - // expect panic if the condition is false. func expect(cond bool, msg string, args ...interface{}) { if !cond { diff --git a/entc/gen/template.go b/entc/gen/template.go index e7e2ab596..b2c0431db 100644 --- a/entc/gen/template.go +++ b/entc/gen/template.go @@ -1,7 +1,12 @@ package gen import ( + "bytes" "fmt" + "go/parser" + "go/token" + "path/filepath" + "strconv" "text/template" ) @@ -73,12 +78,12 @@ var ( { Name: "migrate", Format: "migrate/migrate.go", - Skip: func(g *Graph) bool { return g.migrateSupport() }, + Skip: func(g *Graph) bool { return !g.migrateSupport() }, }, { Name: "schema", Format: "migrate/schema.go", - Skip: func(g *Graph) bool { return g.migrateSupport() }, + Skip: func(g *Graph) bool { return !g.migrateSupport() }, }, { Name: "predicate", @@ -90,8 +95,11 @@ var ( }, } // templates holds the Go templates for the code generation. - // the init function below initializes the templates and its funcs. + // the init function below initializes the templates and its + // funcs to avoid initialization loop. templates = template.New("templates") + // imports are the import packages used for code generation. + imports = make(map[string]string) ) func init() { @@ -99,6 +107,20 @@ func init() { for _, asset := range AssetNames() { templates = template.Must(templates.Parse(string(MustAsset(asset)))) } + b := bytes.NewBuffer([]byte("package main\n")) + check(templates.ExecuteTemplate(b, "import", Type{}), "load imports") + f, err := parser.ParseFile(token.NewFileSet(), "", b, parser.ImportsOnly) + check(err, "parse imports") + for _, spec := range f.Imports { + path, err := strconv.Unquote(spec.Path.Value) + check(err, "unquote import path") + imports[filepath.Base(path)] = path + } + for _, s := range drivers { + for _, path := range s.Imports { + imports[filepath.Base(path)] = path + } + } } func pkgf(s string) func(t *Type) string { diff --git a/entc/gen/template/builder/setter.tmpl b/entc/gen/template/builder/setter.tmpl index 955c5899d..0d945a8a9 100644 --- a/entc/gen/template/builder/setter.tmpl +++ b/entc/gen/template/builder/setter.tmpl @@ -3,14 +3,15 @@ {{ $receiver := receiver $builder }} {{ range $_, $f := $.Fields }} - {{ $p := lower (printf "%.1s" $f.Type) }} + {{ $p := receiver $f.Type.String }} {{ $func := print "Set" (pascal $f.Name) }} // {{ $func }} sets the {{ $f.Name }} field. func ({{ $receiver }} *{{ $builder }}) {{ $func }}({{ $p }} {{ $f.Type }}) *{{ $builder }} { {{ $receiver }}.{{ $f.StructField }} = &{{ $p }} return {{ $receiver }} } - {{ if or $f.Optional $f.Nullable $f.HasDefault }} + {{/* avoid generting nillable setters for slices because the nil value for slice is valid */}} + {{ if and (not $f.Type.Slice) (or $f.Optional $f.Nullable $f.HasDefault) }} {{ $nillableFunc := print "SetNillable" (pascal $f.Name) }} // {{ $nillableFunc }} sets the {{ $f.Name }} field if the given value is not nil. func ({{ $receiver }} *{{ $builder }}) {{ $nillableFunc }}({{ $p }} *{{ $f.Type }}) *{{ $builder }} { diff --git a/entc/gen/type.go b/entc/gen/type.go index fcf15fd14..10951fe1e 100644 --- a/entc/gen/type.go +++ b/entc/gen/type.go @@ -136,23 +136,7 @@ func (t Type) Package() string { return strings.ToLower(t.Name) } // Receiver returns the receiver name of this node. It makes sure the // receiver names doesn't conflict with import names. func (t Type) Receiver() string { - parts := strings.Split(snake(t.Name), "_") - min := len(parts[0]) - for _, w := range parts[1:] { - if len(w) < min { - min = len(w) - } - } - for i := 1; i < min; i++ { - r := parts[0][:i] - for _, w := range parts[1:] { - r += w[:i] - } - if _, ok := t.Config.imports[r]; !ok { - return r - } - } - return strings.ToLower(t.Name) + return receiver(t.Name) } // HasAssoc returns true if this type has an assoc edge with the given name. @@ -317,11 +301,13 @@ func (f Field) Column() *schema.Column { c.Type = field.TypeInt c.Increment = true } - if f.def.Size != nil { - c.Size = *f.def.Size - } - if f.def.Charset != nil { - c.Charset = *f.def.Charset + if f.def != nil { + if f.def.Size != nil { + c.Size = *f.def.Size + } + if f.def.Charset != nil { + c.Charset = *f.def.Charset + } } return c } diff --git a/entc/gen/type_test.go b/entc/gen/type_test.go index 3dcd09558..357494c9f 100644 --- a/entc/gen/type_test.go +++ b/entc/gen/type_test.go @@ -68,9 +68,10 @@ func TestType_Receiver(t *testing.T) { {"PHBUser", "pu"}, {"PHBOrg", "po"}, {"DomainSpecificLang", "dospla"}, + {"[]byte", "b"}, } for _, tt := range tests { - typ := &Type{Name: tt.name, Config: Config{Package: "entc/gen", imports: imports()}} + typ := &Type{Name: tt.name, Config: Config{Package: "entc/gen"}} require.Equal(t, tt.receiver, typ.Receiver()) } } diff --git a/entc/integration/migrate/entv2/example_test.go b/entc/integration/migrate/entv2/example_test.go index 6cf1b2f68..502ed2e51 100644 --- a/entc/integration/migrate/entv2/example_test.go +++ b/entc/integration/migrate/entv2/example_test.go @@ -80,6 +80,7 @@ func ExampleUser() { SetAge(1). SetName("string"). SetPhone("string"). + SetBuffer(1). SaveX(ctx) log.Println("user created:", u) diff --git a/entc/integration/migrate/entv2/migrate/schema.go b/entc/integration/migrate/entv2/migrate/schema.go index b6f3e4af5..823f88140 100644 --- a/entc/integration/migrate/entv2/migrate/schema.go +++ b/entc/integration/migrate/entv2/migrate/schema.go @@ -37,6 +37,7 @@ var ( {Name: "age", Type: field.TypeInt}, {Name: "name", Type: field.TypeString, Size: 2147483647}, {Name: "phone", Type: field.TypeString}, + {Name: "buffer", Type: field.TypeBytes}, } // UsersTable holds the schema information for the "users" table. UsersTable = &schema.Table{ diff --git a/entc/integration/migrate/entv2/schema/user.go b/entc/integration/migrate/entv2/schema/user.go index 582c3fefa..9dbdefe47 100644 --- a/entc/integration/migrate/entv2/schema/user.go +++ b/entc/integration/migrate/entv2/schema/user.go @@ -17,8 +17,10 @@ func (User) Fields() []ent.Field { field.Int("age"), // extending name field to longtext. field.Text("name"), - // adding new column. + // adding new columns. field.String("phone"), + field.Bytes("buffer"). + Default([]byte("{}")), // deleting the address column. } } diff --git a/entc/integration/migrate/entv2/user.go b/entc/integration/migrate/entv2/user.go index e2c22d520..541cac1e3 100644 --- a/entc/integration/migrate/entv2/user.go +++ b/entc/integration/migrate/entv2/user.go @@ -21,15 +21,18 @@ type User struct { Name string `json:"name,omitempty"` // Phone holds the value of the "phone" field. Phone string `json:"phone,omitempty"` + // Buffer holds the value of the "buffer" field. + Buffer []byte `json:"buffer,omitempty"` } // FromRows scans the sql response data into User. func (u *User) FromRows(rows *sql.Rows) error { var vu struct { - ID int - Age int - Name string - Phone string + ID int + Age int + Name string + Phone string + Buffer []byte } // the order here should be the same as in the `user.Columns`. if err := rows.Scan( @@ -37,6 +40,7 @@ func (u *User) FromRows(rows *sql.Rows) error { &vu.Age, &vu.Name, &vu.Phone, + &vu.Buffer, ); err != nil { return err } @@ -44,6 +48,7 @@ func (u *User) FromRows(rows *sql.Rows) error { u.Age = vu.Age u.Name = vu.Name u.Phone = vu.Phone + u.Buffer = vu.Buffer return nil } @@ -73,6 +78,7 @@ func (u *User) String() string { buf.WriteString(fmt.Sprintf(", age=%v", u.Age)) buf.WriteString(fmt.Sprintf(", name=%v", u.Name)) buf.WriteString(fmt.Sprintf(", phone=%v", u.Phone)) + buf.WriteString(fmt.Sprintf(", buffer=%v", u.Buffer)) buf.WriteString(")") return buf.String() } diff --git a/entc/integration/migrate/entv2/user/user.go b/entc/integration/migrate/entv2/user/user.go index 0b6d1fcb1..c9e4b4b40 100644 --- a/entc/integration/migrate/entv2/user/user.go +++ b/entc/integration/migrate/entv2/user/user.go @@ -2,6 +2,10 @@ package user +import ( + "fbc/ent/entc/integration/migrate/entv2/schema" +) + const ( // Label holds the string label denoting the user type in the database. Label = "user" @@ -13,6 +17,8 @@ const ( FieldName = "name" // FieldPhone holds the string denoting the phone vertex property in the database. FieldPhone = "phone" + // FieldBuffer holds the string denoting the buffer vertex property in the database. + FieldBuffer = "buffer" // Table holds the table name of the user in the database. Table = "users" @@ -24,4 +30,11 @@ var Columns = []string{ FieldAge, FieldName, FieldPhone, + FieldBuffer, } + +var ( + fields = schema.User{}.Fields() + // DefaultBuffer holds the default value for the buffer field. + DefaultBuffer = fields[3].Value().([]byte) +) diff --git a/entc/integration/migrate/entv2/user/where.go b/entc/integration/migrate/entv2/user/where.go index 1c0f9b1de..42363a626 100644 --- a/entc/integration/migrate/entv2/user/where.go +++ b/entc/integration/migrate/entv2/user/where.go @@ -145,6 +145,15 @@ func Phone(v string) predicate.User { ) } +// Buffer applies equality check predicate on the "buffer" field. It's identical to BufferEQ. +func Buffer(v []byte) predicate.User { + return predicate.User( + func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldBuffer), v)) + }, + ) +} + // AgeEQ applies the EQ predicate on the "age" field. func AgeEQ(v int) predicate.User { return predicate.User( @@ -475,6 +484,98 @@ func PhoneHasSuffix(v string) predicate.User { ) } +// BufferEQ applies the EQ predicate on the "buffer" field. +func BufferEQ(v []byte) predicate.User { + return predicate.User( + func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldBuffer), v)) + }, + ) +} + +// BufferNEQ applies the NEQ predicate on the "buffer" field. +func BufferNEQ(v []byte) predicate.User { + return predicate.User( + func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldBuffer), v)) + }, + ) +} + +// BufferGT applies the GT predicate on the "buffer" field. +func BufferGT(v []byte) predicate.User { + return predicate.User( + func(s *sql.Selector) { + s.Where(sql.GT(s.C(FieldBuffer), v)) + }, + ) +} + +// BufferGTE applies the GTE predicate on the "buffer" field. +func BufferGTE(v []byte) predicate.User { + return predicate.User( + func(s *sql.Selector) { + s.Where(sql.GTE(s.C(FieldBuffer), v)) + }, + ) +} + +// BufferLT applies the LT predicate on the "buffer" field. +func BufferLT(v []byte) predicate.User { + return predicate.User( + func(s *sql.Selector) { + s.Where(sql.LT(s.C(FieldBuffer), v)) + }, + ) +} + +// BufferLTE applies the LTE predicate on the "buffer" field. +func BufferLTE(v []byte) predicate.User { + return predicate.User( + func(s *sql.Selector) { + s.Where(sql.LTE(s.C(FieldBuffer), v)) + }, + ) +} + +// BufferIn applies the In predicate on the "buffer" field. +func BufferIn(vs ...[]byte) predicate.User { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.User( + 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(FieldBuffer), v...)) + }, + ) +} + +// BufferNotIn applies the NotIn predicate on the "buffer" field. +func BufferNotIn(vs ...[]byte) predicate.User { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.User( + 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(FieldBuffer), v...)) + }, + ) +} + // Or groups list of predicates with the or operator between them. func Or(predicates ...predicate.User) predicate.User { return predicate.User( diff --git a/entc/integration/migrate/entv2/user_create.go b/entc/integration/migrate/entv2/user_create.go index f11717ef8..fc76ce250 100644 --- a/entc/integration/migrate/entv2/user_create.go +++ b/entc/integration/migrate/entv2/user_create.go @@ -16,9 +16,10 @@ import ( // UserCreate is the builder for creating a User entity. type UserCreate struct { config - age *int - name *string - phone *string + age *int + name *string + phone *string + buffer *[]byte } // SetAge sets the age field. @@ -39,6 +40,12 @@ func (uc *UserCreate) SetPhone(s string) *UserCreate { return uc } +// SetBuffer sets the buffer field. +func (uc *UserCreate) SetBuffer(b []byte) *UserCreate { + uc.buffer = &b + return uc +} + // Save creates the User in the database. func (uc *UserCreate) Save(ctx context.Context) (*User, error) { if uc.age == nil { @@ -50,6 +57,10 @@ func (uc *UserCreate) Save(ctx context.Context) (*User, error) { if uc.phone == nil { return nil, errors.New("entv2: missing required field \"phone\"") } + if uc.buffer == nil { + v := user.DefaultBuffer + uc.buffer = &v + } switch uc.driver.Dialect() { case dialect.MySQL, dialect.SQLite: return uc.sqlSave(ctx) @@ -89,6 +100,10 @@ func (uc *UserCreate) sqlSave(ctx context.Context) (*User, error) { builder.Set(user.FieldPhone, *uc.phone) u.Phone = *uc.phone } + if uc.buffer != nil { + builder.Set(user.FieldBuffer, *uc.buffer) + u.Buffer = *uc.buffer + } query, args := builder.Query() if err := tx.Exec(ctx, query, args, &res); err != nil { return nil, rollback(tx, err) diff --git a/entc/integration/migrate/entv2/user_update.go b/entc/integration/migrate/entv2/user_update.go index 7f6b44bf8..8e6b63eb6 100644 --- a/entc/integration/migrate/entv2/user_update.go +++ b/entc/integration/migrate/entv2/user_update.go @@ -20,6 +20,7 @@ type UserUpdate struct { age *int name *string phone *string + buffer *[]byte predicates []predicate.User } @@ -47,6 +48,12 @@ func (uu *UserUpdate) SetPhone(s string) *UserUpdate { return uu } +// SetBuffer sets the buffer field. +func (uu *UserUpdate) SetBuffer(b []byte) *UserUpdate { + uu.buffer = &b + return uu +} + // Save executes the query and returns the number of rows/vertices matched by this operation. func (uu *UserUpdate) Save(ctx context.Context) (int, error) { switch uu.driver.Dialect() { @@ -123,6 +130,10 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { update = true builder.Set(user.FieldPhone, *uu.phone) } + if uu.buffer != nil { + update = true + builder.Set(user.FieldBuffer, *uu.buffer) + } if update { query, args := builder.Query() if err := tx.Exec(ctx, query, args, &res); err != nil { @@ -138,10 +149,11 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { // UserUpdateOne is the builder for updating a single User entity. type UserUpdateOne struct { config - id string - age *int - name *string - phone *string + id string + age *int + name *string + phone *string + buffer *[]byte } // SetAge sets the age field. @@ -162,6 +174,12 @@ func (uuo *UserUpdateOne) SetPhone(s string) *UserUpdateOne { return uuo } +// SetBuffer sets the buffer field. +func (uuo *UserUpdateOne) SetBuffer(b []byte) *UserUpdateOne { + uuo.buffer = &b + return uuo +} + // Save executes the query and returns the updated entity. func (uuo *UserUpdateOne) Save(ctx context.Context) (*User, error) { switch uuo.driver.Dialect() { @@ -244,6 +262,11 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (u *User, err error) { builder.Set(user.FieldPhone, *uuo.phone) u.Phone = *uuo.phone } + if uuo.buffer != nil { + update = true + builder.Set(user.FieldBuffer, *uuo.buffer) + u.Buffer = *uuo.buffer + } if update { query, args := builder.Query() if err := tx.Exec(ctx, query, args, &res); err != nil { diff --git a/entc/integration/migrate/migrate_test.go b/entc/integration/migrate/migrate_test.go index 379324445..e6d982e71 100644 --- a/entc/integration/migrate/migrate_test.go +++ b/entc/integration/migrate/migrate_test.go @@ -74,6 +74,9 @@ func SanityV2(t *testing.T, client *entv2.Client) { u := client.User.Create().SetAge(1).SetName("bar").SetPhone("100").SaveX(ctx) require.Equal(t, 1, u.Age) require.Equal(t, "bar", u.Name) + require.Equal(t, []byte("{}"), u.Buffer) + u = u.Update().SetBuffer([]byte("[]")).SaveX(ctx) + require.Equal(t, []byte("[]"), u.Buffer) _, err := client.User.Create().SetAge(1).SetName("foobarbazqux").SetPhone("100").Save(ctx) require.NoError(t, err, "name is not limited to 10 chars") diff --git a/field/field.go b/field/field.go index 179ef5f07..678a6944a 100644 --- a/field/field.go +++ b/field/field.go @@ -16,6 +16,7 @@ const ( TypeInvalid Type = iota TypeBool TypeTime + TypeBytes TypeString TypeInt8 TypeInt16 @@ -42,21 +43,29 @@ func (t Type) String() string { // Valid reports if the given type if known type. func (t Type) Valid() bool { return t > TypeInvalid && t < endTypes } -// Numeric reports of the given type is a numeric type. +// Numeric reports if the given type is a numeric type. func (t Type) Numeric() bool { return t >= TypeInt && t < endTypes } +// Slice reports if the given type is a slice type. +func (t Type) Slice() bool { return t == TypeBytes } + // ConstName returns the constant name of a type. It's used by entc for printing the constant name in templates. func (t Type) ConstName() string { - if t == TypeTime { + switch t { + case TypeTime: return "TypeTime" + case TypeBytes: + return "TypeBytes" + default: + return "Type" + strings.Title(t.String()) } - return "Type" + strings.Title(t.String()) } var typeNames = [...]string{ TypeInvalid: "invalid", TypeBool: "bool", TypeTime: "time.Time", + TypeBytes: "[]byte", TypeString: "string", TypeInt: "int", TypeInt8: "int8", @@ -113,6 +122,10 @@ func Text(name string) *stringBuilder { return &stringBuilder{Field{typ: TypeString, name: name, size: math.MaxInt32}} } +// Bytes returns a new Field with type bytes/buffer. +// In MySQL and SQLite, it is the "BLOB" type, and it does not support for Gremlin. +func Bytes(name string) *bytesBuilder { return &bytesBuilder{Field{typ: TypeBytes, name: name}} } + // Bool returns a new Field with type bool. func Bool(name string) *boolBuilder { return &boolBuilder{Field{typ: TypeBool, name: name}} } @@ -480,6 +493,42 @@ func (b *boolBuilder) StructTag(s string) *boolBuilder { return b } +// bytesBuilder is the builder for bytes fields. +type bytesBuilder struct { + Field +} + +// Default sets the default value of the field. +func (b *bytesBuilder) Default(v []byte) *bytesBuilder { + b.value = v + return b +} + +// Nullable indicates that this field is nullable. +// Unlike "Optional", nullable fields are pointers in the generated field. +func (b *bytesBuilder) Nullable() *bytesBuilder { + b.nullable = true + return b +} + +// Optional indicates that this field is optional on create. +// Unlike edges, fields are required by default. +func (b *bytesBuilder) Optional() *bytesBuilder { + b.optional = true + return b +} + +// Comment sets the comment of the field. +func (b *bytesBuilder) Comment(c string) *bytesBuilder { + return b +} + +// StructTag sets the struct tag of the field. +func (b *bytesBuilder) StructTag(s string) *bytesBuilder { + b.tag = s + return b +} + // Charseter is the interface that wraps the Charset method. type Charseter interface { Charset() string diff --git a/field/field_test.go b/field/field_test.go index 26a7d6102..a3a0c3710 100644 --- a/field/field_test.go +++ b/field/field_test.go @@ -50,6 +50,14 @@ func TestBool(t *testing.T) { assert.Equal(t, true, f.Value()) } +func TestBytes(t *testing.T) { + f := field.Bytes("active").Default([]byte("{}")) + assert.Equal(t, "active", f.Name()) + assert.Equal(t, field.TypeBytes, f.Type()) + assert.True(t, f.HasDefault()) + assert.Equal(t, []byte("{}"), f.Value()) +} + func TestString(t *testing.T) { re := regexp.MustCompile("[a-zA-Z0-9]") f := field.String("name").Unique().Match(re).Validate(func(string) error { return nil })