Repository: ent/ent

Issue:
override id column
Is there a way to redeclare the id column as a custom string type? 

If I put the following in my schema: 

`field.String("id").Unique()`

The generated code ends up broken because it creates two declarations of "id", but furthermore it ignores the fact that it's a string, and declares it as an integer instead. 

This might be related to the UUID discussions, but it would be great if we can define our own custom primary keys as strings that we provide at runtime. 

Discussion:
@alexsn: Currently the "id" field is implied so there's no way to override the underlying storage key. The above setting should give a better error message where a reserved field name is used.

Are you looking to rename the "id" column name, provide your own PKs or both?@a8m: This is part of our roadmap and it should be addressed in the upcoming week (or 2).
I linked this issue to the roadmap item. @marwan-at-work: @alexsn im not looking to rename "id". I'm looking to keep it "id" but change its type from integer to string, and also be able to provide my own value when i create a row. 

Thanks :v: @BitPhinix: Seems to work with the latest matcher branch, but there isn't a uuid type available. Is there any way to use a Postgres UUID primary key with ent?@a8m: Hey @BitPhinix, this feature is still in development. 
I'll update the issue (and the roadmap) once we finish the implementation. @aca: @a8m Feature Proposal 
I think it is related to this, so I write it here.
Can we customize struct tag for field "id" ?

I'm currently using type generated by ent, and I want to make json reponse with no ID field.
Which is achievable by Sensitive() for other fields.
But It seems it is impossible to make edit struct tag for ID field.

```
type Project struct {
    ent.Project
    ID `json:"-"`
}
```
I thought I could solve this by doing something like this. But didn't worked well.@a8m: Hey @aca, did you try to override the struct tag? See - https://entgo.io/docs/schema-fields/#struct-tags@aca: @a8m I meant ID field which is generated by entc not by defining ent.Field
```
// Fields of the Namespace.
func (Namespace) Fields() []ent.Field {
	return []ent.Field{
		field.String("name").Unique().Match(regexp.MustCompile("[a-z0-9]+(?:[._-][a-z0-9]+)*")).NotEmpty(),
		field.Int64("id").StructTag("-").Unique(),
	}
}
```
Defining "id" field doesn't seems to work. Really sorry If I misunderstood. 


@a8m: There's no option to override the ID's struct-tag except using custom template (which is too complex in this case imo). 

This definitely should be addressed. I'll update the issue once we get to it.  @gukz: Hey, dear @a8m , I am rewriting a project with go , I enjoy using ent, but this project has a table with a `user_id` primary key. Can ent allow specify any field as the primary key? @a8m: > Hey, dear @a8m , I am rewriting a project with go , I enjoy using ent, but this project has a table with a user_id primary key. Can ent allow specify any field as the primary key?

I ran the codegen with this option in the schema and it worked for me.
```go
func (User) Fields() []ent.Field {
	return []ent.Field{
		field.Int("id").
			StorageKey("user_id"),
		// more fields..
	}
}
```
The database in MySQL was created as follows:
![image](https://user-images.githubusercontent.com/7413593/72155349-9cc0bf80-33bb-11ea-865f-c0f4a190f52a.png)


You are welcome to try it. 

Like I mentioned above, once we get to this task, we'll handle all these cases.
Thanks!@marwan-at-work: I know the issue is still being worked on, but just as an update: 
Defining an ID as a string no longer creates a build error, but it now generates a runtime error when running a migration. 

Given the following `field.String("id").Unique()`

The following error shows up from the create table operation: 

`sql/schema: create table "specs": Error 1071: Specified key was too long; max key length is 767 bytes`

The interesting part, is that if I change the schema to say: `field.String("id").Unique().MaxLen(25)`

Then the same error happens. I noticed that the `schema.go` file doesn't populate the "id" Size the same way it would do for other fields. 

If I manually change `schema.go` inside the migrate directory to include `Size: 25` then the migration successfully works. @marwan-at-work: @a8m another issue I discovered (just in case you missed it), is that `where.go` tries to convert an ID string to an integer while ignoring the error, which results in always bringing back the first row. 

```golang
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id string) predicate.Spec {
	return predicate.Spec(func(s *sql.Selector) {
		id, _ := strconv.Atoi(id)
		s.Where(sql.EQ(s.C(FieldID), id))
	},
	)
}
```

This should just be 

```golang
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id string) predicate.Spec {
	return predicate.Spec(func(s *sql.Selector) {
		s.Where(sql.EQ(s.C(FieldID), id))
	},
	)
}
```

But in what scenario do you expect an ID that is a string but must be converted to an integer? Will removing that one line completely not be sufficient? 

Thanks! @a8m: Hey @marwan-at-work and thanks for reporting these issues. Regarding your comments:

> Given the following field.String("id").Unique()
>
> The following error shows up from the create table operation:
>
> sql/schema: create table "specs": Error 1071: Specified key was too long; max key length is 767 bytes

I'm aware about this and it should be addressed. I'm pretty sure this is the last thing left for closing this issue. 

> @a8m another issue I discovered (just in case you missed it), is that where.go tries to convert an ID string to an integer while ignoring the error, which results in always bringing back the first row.

Where did you encounter this code? in your generated code or in this repository? If you don't run codegen with `--idtype string`, the logic shouldn't be there. 

This (legacy) option was added to allow using the same generate `ent` package for both AWS Neptune (`gremlin`) and SQL dialects. Neptune uses UUID strings for its IDs, and MySQL is configured with numeric types for its PKs (in our deployment). Therefore, the type in the code must be string, and we convert it to int before/after the data were loaded. 

I don't see any reason for using this option, except our exceptional case that was used for migrating from Neptune to MySQL. I'll suggest to not use it because we plan to drop it soon. 

@marwan-at-work: @a8m 

> Where did you encounter this code?

I'm using the latest commit of `entc` and I was not aware of the `--idtype` flag at all. So I was just using `entc generate ./ent/schema` 

I created a reproduction repository here: https://github.com/marwan-at-work/entrepro

You can see that my schema has the following ID definition https://github.com/marwan-at-work/entrepro/blob/master/ent/schema/spec.go#L14:L22

But then then the code that was generated from `entc generate ./ent/schema` gave these results: https://github.com/marwan-at-work/entrepro/blob/master/ent/spec/where.go#L21:L26

If that can just be removed from the template, I'm happy to contribute a fix, here's what I did locally and it seemed to work: https://github.com/marwan-at-work/ent/commit/e3b004b07b1ee6074d9363b7e49cbaaf4a7228d8#diff-a01877e4cc82393fb3d3f48188992b02

Thanks!@a8m: Thanks for helping debugging this @marwan-at-work.
That looks like a bug (I need to deep dive into it), but it can't be solved with just removing this part from the template (for the reason I've commented above). 

Can you please run the codegen as describe [here](https://entgo.io/docs/code-gen/#version-compatibility-between-entc-and-ent), or just use:
```
go run github.com/facebookincubator/ent/cmd/entc generate ./ent/schema
```
Just to make sure we're running the same version (as in go.mod). 

I'll update once it's fixed. @a8m: @marwan-at-work - I've added a fix for this, and checked it on your repo using this version: `github.com/facebookincubator/ent@9733051cc361`.

Please check it and update me if there's any issue with this. @marwan-at-work: @a8m just tried it with that commit and it looks great!!@a8m: All comments that were raised in this issue addressed (hopefully), although there are some docs changes that need to be done.

Please give it a try and update us if there's any issue with this. Closing.@jiamo: If we have multi schema in same dic. I only want one schema replace `id`. Can we put `idtype` in schema file instead using option `--idtype`?@nvhai245: I think we could support some options to define a different primary key for schema, for example:
`field.String("uid").PrimaryKey()`@a8m: You can. See the second option here: https://entgo.io/docs/schema-fields#id-field@seinshah: This works as you mentioned, but just for your information, generating migration diff for the following scenario is not as expected:

- Already have an int primary key (identity)
- Deciding to remove that column
- Use the existing `oid` column as the new key by renaming it internally to `id` and setting `StorageKey` as `oid`

Actual Migration File: `ALTER TABLE ADD PRIMARY KEY ...`
Expected Migration File: `ALTER TABLE ... DROP COLUMN "id"`

I confirmed that `id` is removed from table columns in the generated `.../migrate/schema.go`. I also confirmed there is no change when adding `with-drop-column` and the unique key on the oid column is droped if I use `with-drop-index` options.

Solution patch:
diff --git a/entc/gen/internal/bindata.go b/entc/gen/internal/bindata.go
index 83906812e..264805366 100644
--- a/entc/gen/internal/bindata.go
+++ b/entc/gen/internal/bindata.go
@@ -741,7 +741,7 @@ func templateDialectSqlOpenTmpl() (*asset, error) {
 	return a, nil
 }
 
-var _templateDialectSqlPredicateTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x96\x5d\x4f\xe3\x38\x17\xc7\xaf\x9b\x4f\x71\x84\x90\x9e\x04\x15\x17\xb8\x7b\x56\x62\x25\xd4\x6d\xb5\xd5\xce\x14\x76\x8b\x76\x2e\x10\x1a\x99\xf8\x24\xb1\xc6\xd8\xc6\x76\x53\x55\x51\xbe\xfb\xca\x4e\x9a\xa6\xa5\x0c\x2c\xec\x4a\x7b\x31\x77\xe0\xf3\xe2\x73\xfe\xe7\x77\xdc\x54\xd5\xe8\x24\x1a\x2b\xbd\x36\x3c\x2f\x1c\x5c\x9c\x9d\xff\xff\x54\x1b\xb4\x28\x1d\x4c\x69\x8a\x0f\x4a\x7d\x83\x99\x4c\x09\x5c\x09\x01\xc1\xc9\x82\xb7\x9b\x12\x19\x89\x6e\x0b\x6e\xc1\xaa\xa5\x49\x11\x52\xc5\x10\xb8\x05\xc1\x53\x94\x16\x19\x2c\x25\x43\x03\xae\x40\xb8\xd2\x34\x2d\x10\x2e\xc8\xd9\xc6\x0a\x99\x5a\x4a\x16\x71\x19\xec\x9f\x66\xe3\xc9\x7c\x31\x81\x8c\x0b\x84\xf6\xcc\x28\xe5\x80\x71\x83\xa9\x53\x66\x0d\x2a\x03\xd7\xbb\xcc\x19\x44\x12\x9d\x8c\xea\x3a\x8a\xaa\x0a\x18\x66\x5c\x22\x1c\x31\x4e\x05\xa6\x6e\x64\x9f\xc4\x48\x1b\x64\x3c\xa5\x0e\x47\x9c\x1d\xc1\x69\x5d\x47\x83\x6c\x29\xd3\xd8\xc2\x89\x7d\x12\x64\x81\x22\xa4\x4e\xa0\x8a\x06\x83\xaa\x3a\x05\x9e\xc1\x31\x99\xfd\x42\x66\x76\xe1\x0c\x97\x39\xd4\x35\x67\x43\xf8\x0a\x3f\x5d\x82\x75\x26\x55\xb2\x24\x57\x4e\xf1\x98\xb3\xc4\xfb\xa3\x64\xe0\xb3\x0e\x2c\xf9\x52\xa0\xc1\xd8\xa7\x9d\xfc\x1e\x5b\x32\x8e\xab\xaa\xc9\x35\x56\xd2\x3a\x2a\x1d\xd4\x75\x32\x04\xce\x92\x24\x1a\xd4\x51\x2f\xfa\x2d\xd5\x8f\x94\xb6\x6d\x07\x3e\xf2\x58\x69\x5f\xd2\x31\x59\xa4\x4a\x23\xb9\xd6\x3d\x13\x35\x79\xdf\x76\x65\xf2\x9e\xd1\x3a\x65\x68\x8e\x7d\x87\x45\x7b\xf4\x46\x79\x94\x26\x7f\x52\xc3\x29\xe3\x69\xd3\xfa\x60\x34\xf2\x06\xa9\x1c\x50\x93\x2f\x1f\x51\x3a\x0b\x2b\x34\x08\xda\xa8\x92\x33\x64\x43\xa0\x5a\xfb\x66\xfd\x50\xa7\x57\x9f\x16\x13\x48\x5b\x51\xec\xb0\xcd\x60\xb9\x4c\x11\x56\x08\x29\x95\xff\x73\x3e\x40\xac\xe1\x68\x36\x87\x38\x39\x22\x10\x20\x5b\x71\x21\xe0\x91\x7e\xc3\x06\x83\x4e\x1e\xc8\xa8\xb0\x6b\xe2\x13\xf1\x0c\x04\xca\x20\xbd\x97\xa1\xae\x13\xb8\xbc\x84\xb3\xd0\xc0\xee\x90\xa6\x54\x58\x8c\xfd\x2c\x06\x83\x81\x41\xb7\x34\xd2\xff\x19\x1a\x2a\xbd\x3c\xfe\xa2\xf8\xee\x9e\x4b\x87\x26\xa3\x29\x56\xf5\x70\x3f\x77\x08\xce\x94\x01\xee\x03\x0c\x95\x39\x42\xd9\xde\x55\x55\x87\x60\x2a\xef\xf8\xbd\xc7\x69\x8f\xa6\x6d\xce\x3b\x7e\x9f\x54\x15\xa0\xb0\xd8\xba\xc3\x25\xec\x98\xbd\x75\x43\x5d\x28\x37\x90\xe4\xfd\x0f\xdc\x17\x34\x39\x08\x70\xaf\x8d\x4d\x8e\x43\x2c\x57\x15\xa4\x54\x88\x0e\x1c\x72\xad\xc7\x7e\xc9\x3d\x80\x75\xfd\x1d\xce\xdb\xfe\x77\x69\x29\x09\x21\xdb\xee\x38\xeb\x7a\x79\xc7\x4e\x64\x1c\x05\xeb\xaf\x44\xd6\x87\x7a\xea\xad\xaf\x21\xfd\xc2\xd2\x66\x7b\xad\x94\xef\xad\x6e\x7f\x69\x5f\xaa\xf0\xc7\x46\xff\xcb\x1b\xfd\x51\xbc\x77\x89\x68\xd0\xf6\xea\x78\xe9\xe6\x5c\xb4\xca\x0d\xa1\x3c\x48\x7d\x0b\x7d\xb8\xff\x23\xc4\x23\xcb\x71\x54\xd0\x1d\xa4\x76\xe6\x3e\x61\xaf\x0f\xdd\x3a\x0c\xa0\xd9\x27\x91\x1b\xaa\x0b\x32\xc7\xd5\xc2\xa1\x8e\xbd\x56\xdd\xe1\xd4\xa8\xc7\xf8\x96\x3e\x08\x0c\x9b\xfc\x7c\xbf\x77\xbc\x6f\x55\x50\x09\x49\x88\xe8\xf9\xbd\x25\xd8\x17\x1d\x77\xff\x35\x79\xfe\x40\x41\x6e\xd7\x1a\xbb\x14\x48\x66\x76\x26\x4b\x34\xb6\x7f\xf6\xec\xba\xf6\xe5\x6d\xb0\x46\xf2\xf9\xe2\x73\x23\x47\xfb\x20\x1f\x23\xb9\xf9\xad\xe7\x4f\x08\xe9\x22\xc2\x9b\xb4\xe7\x3c\x56\x62\xf9\x28\x7b\x01\x5b\x6f\xc9\x36\xce\xa1\x1d\x0f\x5d\xd7\xc3\xaf\xd4\xce\x91\xe7\xc5\x83\x32\x36\xb6\x43\xf0\x92\xbf\x7f\xda\x2b\xee\x8a\xff\xe8\xc4\xfd\x16\x20\x1c\x37\x73\x08\x03\x59\xeb\x76\x2a\x0d\xea\x7e\x6e\xcd\xd4\xf6\x47\xb5\xfd\x15\x08\x96\x6e\x2d\x7e\x10\xf3\x85\xbb\x62\x43\xcd\x10\x5e\x1e\x6b\xf8\xf0\xf8\x3a\x04\xbd\xfd\xf6\xf0\xf0\xd8\xf6\x65\xd4\xb1\x4d\x36\xcf\x5f\xfd\xf7\xe9\xa3\xf2\xf5\x0f\x66\x7b\x1e\xb0\x22\x63\xa1\x24\xc6\x09\x59\xa0\xbb\x89\x25\x17\xfe\xde\xc3\xc5\x85\xdc\x6d\x85\x3a\xb6\xe7\xde\x73\xe7\x49\x3e\x27\x37\xf1\x3b\x5e\x46\x65\x3e\x5c\x2c\xff\x6e\xb1\x3c\x03\x0e\x3f\x6f\x7f\x76\xce\xc9\xb5\x89\x3b\x7d\xff\xd1\x5e\xa4\x72\xaf\x36\xa3\x63\x4b\xe6\xca\x3d\x4f\xff\x57\x00\x00\x00\xff\xff\xf2\x53\x0f\xb5\xcc\x0d\x00\x00")
+var _templateDialectSqlPredicateTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x56\x5d\x4f\x23\x37\x14\x7d\x4e\x7e\xc5\x15\x42\xaa\x07\x05\x07\x78\x6b\x25\x2a\xa1\x2c\xa8\x51\x77\x03\x6d\x68\xf7\x01\xa1\x95\x19\xdf\x99\xb1\xd6\xd8\xc6\x76\x06\x45\xa3\xf9\xef\x95\x3d\x93\xc9\x24\x84\x85\x42\x2b\xed\xc3\xbe\x81\xef\xf7\x39\xe7\xde\x4c\x55\x8d\x0f\x86\x13\x6d\x96\x56\xe4\x85\x87\x93\xa3\xe3\x9f\x0f\x8d\x45\x87\xca\xc3\x05\x4b\xf1\x4e\xeb\xaf\x30\x55\x29\x85\x33\x29\x21\x3a\x39\x08\x76\x5b\x22\xa7\xc3\xeb\x42\x38\x70\x7a\x61\x53\x84\x54\x73\x04\xe1\x40\x8a\x14\x95\x43\x0e\x0b\xc5\xd1\x82\x2f\x10\xce\x0c\x4b\x0b\x84\x13\x7a\xb4\xb2\x42\xa6\x17\x8a\x0f\x85\x8a\xf6\x8f\xd3\xc9\xf9\x6c\x7e\x0e\x99\x90\x08\xed\x9b\xd5\xda\x03\x17\x16\x53\xaf\xed\x12\x74\x06\xbe\x57\xcc\x5b\x44\x3a\x3c\x18\xd7\xf5\x70\x58\x55\xc0\x31\x13\x0a\x61\x8f\x0b\x26\x31\xf5\x63\xf7\x20\xc7\xc6\x22\x17\x29\xf3\x38\x16\x7c\x0f\x0e\xeb\x7a\x38\xc8\x16\x2a\x25\x0e\x0e\xdc\x83\xa4\x73\x94\x31\x75\x02\xd5\x70\x30\xa8\xaa\x43\x10\x19\x30\xc5\x61\x9f\x4e\x3f\xd0\xa9\x9b\x7b\x2b\x54\x0e\x44\x69\xdf\x3c\xfd\xe5\xd0\x7e\x88\x75\x78\x02\x75\x2d\xf8\x08\xbe\xc0\x2f\xa7\xe0\xbc\x4d\xb5\x2a\xe9\x99\xd7\x82\x08\x9e\x84\x54\xa8\x38\x84\x82\x03\x47\x3f\x17\x68\x91\x84\x8a\xe7\x7f\x10\x47\x27\xa4\xaa\x9a\x7c\x13\xad\x9c\x67\xca\x43\x5d\x27\x23\x10\x3c\x49\x86\x83\x7a\xd8\x8b\x7e\xcd\x60\x63\x6d\x5c\x3b\x5c\x88\xdc\xd7\x26\xb4\xb4\x4f\xe7\xa9\x36\x48\x2f\x4d\xcf\xc4\x6c\xde\xb7\x9d\xd9\xbc\x67\x74\x5e\x5b\x96\x63\xdf\x61\xde\x3e\xad\x9d\xc2\x98\x82\x07\x9f\x57\xe3\xf4\x3a\xd8\xf7\xb5\xa1\x7f\x33\x2b\x18\x17\x69\x83\xdb\x60\x3c\x0e\x86\x90\x94\xd9\x7c\x71\x8f\xca\x3b\x78\x44\x8b\x60\xac\x2e\x05\x47\x3e\x02\x66\x4c\x40\x2a\x88\xe5\xe2\xec\xe3\xfc\x1c\xd2\x16\x51\x37\x6a\x33\x38\xa1\x52\x84\x47\x84\x94\xa9\x9f\x7c\x08\x90\x4b\xd8\x9b\xce\x80\x24\x7b\x14\xa2\x78\x1f\x85\x94\x70\xcf\xbe\x62\x23\xaf\x0e\x5b\xc8\x98\x74\x4b\x1a\x12\x89\x0c\x24\xaa\xc8\x5b\xc0\xb0\xae\x13\x38\x3d\x85\xa3\x38\xc0\x26\xc3\x17\x4c\x3a\x24\x81\xc8\xc1\x60\x60\xd1\x2f\xac\x0a\x7f\xc6\x81\xca\x80\x5b\x28\x44\x6e\x6e\x85\xf2\x68\x33\x96\x62\x55\x8f\xb6\x73\xc7\xe0\x4c\x5b\x10\x21\xc0\x32\x95\x23\x94\x6d\xad\x15\x5c\x2d\x11\x75\x5d\xde\x88\xdb\x20\xc3\x2d\x15\xae\xd3\xdd\x88\xdb\xa4\xaa\x00\xa5\xc3\xd6\x1d\x4e\x61\xc3\x1c\xac\x2b\xb5\xc6\x4e\xa3\x02\x83\xff\x46\xa5\x08\xc4\x4e\xc9\xf7\x7a\x5f\x45\xef\x52\x7f\x55\x41\xca\xa4\xec\xa4\x46\x2f\xcd\x24\x5c\x8c\x20\xd9\xba\xfe\xc6\x66\x54\xd5\x0e\x89\x94\x94\xd2\xf5\x5c\x82\x77\x53\xbc\x61\x8b\x32\x81\x92\xf7\x97\x28\xeb\xaf\xc1\x45\xb0\xbe\xa4\xe3\x67\xd6\x3c\xdb\x1a\xa5\x7c\x6b\x77\xdb\x6b\xfe\x5c\x87\xff\xeb\x0d\xf8\xb1\xc6\xef\x97\xf7\xa6\x22\x1a\x69\xc7\xcb\xa9\x0d\x9d\x09\xd9\x22\x37\x82\x72\xa7\xea\x5b\xd1\xc7\xfa\xef\x51\x3c\xf2\x1c\xc7\x05\xdb\x90\xd4\x06\xef\xe7\xfc\x65\xd2\x9d\xc7\x28\x34\xf7\x20\x73\xcb\x4c\x41\x67\xf8\x38\xf7\x68\x48\xc0\xaa\x7b\xbc\xb0\xfa\x9e\x5c\xb3\x3b\x89\x71\x93\x9f\xee\xf7\x86\xf7\xb5\x8e\x28\x21\x8d\x11\x3d\xbf\xd7\x04\x87\xa6\x49\xf7\x5f\x93\xe7\x4f\x94\xf4\x7a\x69\xb0\x4b\x81\x74\xea\xa6\xaa\x44\xeb\xfa\x6f\x4f\xca\x6d\x9c\x5b\xa4\x9f\x4e\x3e\x35\x70\x34\xcf\xe1\xe9\xea\xf7\x9e\x3f\xa5\xb4\x8b\x88\x37\x69\xcb\x79\xa2\xe5\xe2\x5e\xf5\x02\xd6\xde\x8a\xaf\x9c\xe3\x38\x41\x74\xdd\x0c\xbf\x31\x37\x43\x91\x17\x77\xda\x3a\xe2\x46\x10\x20\x7f\x3b\xdb\x8f\xc2\x17\xdf\x29\xe3\x61\x0b\x10\xf6\x1b\x1e\x22\x21\x4b\xd3\xb2\xd2\x48\x3d\xf0\xd6\xb0\xb6\x4d\xd5\xfa\x57\x20\x5a\xba\xb5\xf8\xa1\x98\xcf\xc2\x17\x2b\xd5\x8c\xe0\x79\x5a\xe3\xd7\xc6\x97\x11\x98\xf5\x07\x47\x10\x8f\x6b\x2f\xa3\x21\x2e\x59\x9d\xbf\xfa\xdf\xab\x8f\xa9\x97\xbf\xbe\xdd\x71\x94\x15\x9d\x48\xad\x90\x24\x74\x8e\xfe\x8a\x28\x21\x43\xdd\xdd\xcd\xc5\xdc\x6d\x87\x86\xb8\xe3\xe0\xb9\x71\x92\x8f\xe9\x15\x79\xc3\x65\xd4\xf6\xdd\xcd\x8a\x6f\x36\x2b\x32\x10\xf0\xeb\xfa\x67\xe7\x98\x5e\x5a\xd2\xe1\xfb\x9f\xce\xa2\xb4\x7f\x71\x18\x43\x1c\x9d\x69\xff\x34\xfd\x3f\x01\x00\x00\xff\xff\x38\xb0\xba\x66\x19\x0e\x00\x00")
 
 func templateDialectSqlPredicateTmplBytes() ([]byte, error) {
 	return bindataRead(
@@ -756,7 +756,7 @@ func templateDialectSqlPredicateTmpl() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindataFileInfo{name: "template/dialect/sql/predicate.tmpl", size: 3532, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
+	info := bindataFileInfo{name: "template/dialect/sql/predicate.tmpl", size: 3609, mode: os.FileMode(420), modTime: time.Unix(1, 0)}
 	a := &asset{bytes: bytes, info: info}
 	return a, nil
 }
diff --git a/entc/gen/template/dialect/sql/predicate.tmpl b/entc/gen/template/dialect/sql/predicate.tmpl
index c597fb69d..2095bb494 100644
--- a/entc/gen/template/dialect/sql/predicate.tmpl
+++ b/entc/gen/template/dialect/sql/predicate.tmpl
@@ -6,7 +6,7 @@ in the LICENSE file in the root directory of this source tree.
 
 {{ define "dialect/sql/predicate/id" -}}
 	func(s *sql.Selector) {
-		{{- if $.ID.IsString }}id, _ := strconv.Atoi(id){{- end }}
+		{{- if and $.ID.IsString (not $.ID.UserDefined) }}id, _ := strconv.Atoi(id){{- end }}
 		s.Where(sql.EQ(s.C({{ $.ID.Constant }}), id))
 	}
 {{- end }}
@@ -15,6 +15,7 @@ in the LICENSE file in the root directory of this source tree.
 	{{- $op := $.Scope.Op -}}
 	{{- $arg := $.Scope.Arg -}}
 	{{- $storage := $.Scope.Storage -}}
+	{{- $convid := and $.ID.IsString (not $.ID.UserDefined) -}}
 	func(s *sql.Selector) {
 		{{- if $op.Variadic }}
 			// if not arguments were provided, append the FALSE constants,
@@ -25,9 +26,9 @@ in the LICENSE file in the root directory of this source tree.
 			}
 			v := make([]interface{}, len({{ $arg }}))
 			for i := range v {
-				{{ if $.ID.IsString }}v[i], _ = strconv.Atoi({{ $arg }}[i]){{ else }}v[i] = {{ $arg }}[i]{{ end }}
+				{{- if $convid }}v[i], _ = strconv.Atoi({{ $arg }}[i]){{ else }}v[i] = {{ $arg }}[i]{{ end }}
 			}
-		{{- else if $.ID.IsString }}
+		{{- else if $convid }}
 			id, _ := strconv.Atoi({{ $arg }})
 		{{- end }}
 		s.Where(sql.{{ call $storage.OpCode $op }}(s.C({{ $.ID.Constant }}), {{ if $op.Variadic }}v...{{ else }}id{{ end }}))
diff --git a/entc/gen/type.go b/entc/gen/type.go
index a7a9bd7f5..e8fe385c1 100644
--- a/entc/gen/type.go
+++ b/entc/gen/type.go
@@ -558,10 +558,8 @@ func (f Field) Column() *schema.Column {
 		Nullable: f.Optional,
 		Enums:    f.Enums(),
 	}
-	if f.def != nil {
-		if f.def.Size != nil {
-			c.Size = *f.def.Size
-		}
+	if f.def != nil && f.def.Size != nil {
+		c.Size = *f.def.Size
 	}
 	if f.Default && !f.IsTime() {
 		c.Default = f.DefaultName()
@@ -581,6 +579,10 @@ func (f Field) PK() *schema.Column {
 	if f.UserDefined && !f.Type.Numeric() {
 		c.Increment = false
 		c.Type = f.Type.Type
+		c.Unique = f.Unique
+		if f.def != nil && f.def.Size != nil {
+			c.Size = *f.def.Size
+		}
 	}
 	return c
 }
diff --git a/entc/integration/customid/ent/client.go b/entc/integration/customid/ent/client.go
index 18c1ea3e6..9398f443c 100644
--- a/entc/integration/customid/ent/client.go
+++ b/entc/integration/customid/ent/client.go
@@ -16,6 +16,7 @@ import (
 
 	"github.com/facebookincubator/ent/entc/integration/customid/ent/blob"
 	"github.com/facebookincubator/ent/entc/integration/customid/ent/group"
+	"github.com/facebookincubator/ent/entc/integration/customid/ent/pet"
 	"github.com/facebookincubator/ent/entc/integration/customid/ent/user"
 
 	"github.com/facebookincubator/ent/dialect"
@@ -32,6 +33,8 @@ type Client struct {
 	Blob *BlobClient
 	// Group is the client for interacting with the Group builders.
 	Group *GroupClient
+	// Pet is the client for interacting with the Pet builders.
+	Pet *PetClient
 	// User is the client for interacting with the User builders.
 	User *UserClient
 }
@@ -45,6 +48,7 @@ func NewClient(opts ...Option) *Client {
 		Schema: migrate.NewSchema(c.driver),
 		Blob:   NewBlobClient(c),
 		Group:  NewGroupClient(c),
+		Pet:    NewPetClient(c),
 		User:   NewUserClient(c),
 	}
 }
@@ -79,6 +83,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
 		config: cfg,
 		Blob:   NewBlobClient(cfg),
 		Group:  NewGroupClient(cfg),
+		Pet:    NewPetClient(cfg),
 		User:   NewUserClient(cfg),
 	}, nil
 }
@@ -100,6 +105,7 @@ func (c *Client) Debug() *Client {
 		Schema: migrate.NewSchema(cfg.driver),
 		Blob:   NewBlobClient(cfg),
 		Group:  NewGroupClient(cfg),
+		Pet:    NewPetClient(cfg),
 		User:   NewUserClient(cfg),
 	}
 }
@@ -251,6 +257,70 @@ func (c *GroupClient) QueryUsers(gr *Group) *UserQuery {
 	return query
 }
 
+// PetClient is a client for the Pet schema.
+type PetClient struct {
+	config
+}
+
+// NewPetClient returns a client for the Pet from the given config.
+func NewPetClient(c config) *PetClient {
+	return &PetClient{config: c}
+}
+
+// Create returns a create builder for Pet.
+func (c *PetClient) Create() *PetCreate {
+	return &PetCreate{config: c.config}
+}
+
+// Update returns an update builder for Pet.
+func (c *PetClient) Update() *PetUpdate {
+	return &PetUpdate{config: c.config}
+}
+
+// UpdateOne returns an update builder for the given entity.
+func (c *PetClient) UpdateOne(pe *Pet) *PetUpdateOne {
+	return c.UpdateOneID(pe.ID)
+}
+
+// UpdateOneID returns an update builder for the given id.
+func (c *PetClient) UpdateOneID(id string) *PetUpdateOne {
+	return &PetUpdateOne{config: c.config, id: id}
+}
+
+// Delete returns a delete builder for Pet.
+func (c *PetClient) Delete() *PetDelete {
+	return &PetDelete{config: c.config}
+}
+
+// DeleteOne returns a delete builder for the given entity.
+func (c *PetClient) DeleteOne(pe *Pet) *PetDeleteOne {
+	return c.DeleteOneID(pe.ID)
+}
+
+// DeleteOneID returns a delete builder for the given id.
+func (c *PetClient) DeleteOneID(id string) *PetDeleteOne {
+	return &PetDeleteOne{c.Delete().Where(pet.ID(id))}
+}
+
+// Create returns a query builder for Pet.
+func (c *PetClient) Query() *PetQuery {
+	return &PetQuery{config: c.config}
+}
+
+// Get returns a Pet entity by its id.
+func (c *PetClient) Get(ctx context.Context, id string) (*Pet, error) {
+	return c.Query().Where(pet.ID(id)).Only(ctx)
+}
+
+// GetX is like Get, but panics if an error occurs.
+func (c *PetClient) GetX(ctx context.Context, id string) *Pet {
+	pe, err := c.Get(ctx, id)
+	if err != nil {
+		panic(err)
+	}
+	return pe
+}
+
 // UserClient is a client for the User schema.
 type UserClient struct {
 	config
diff --git a/entc/integration/customid/ent/migrate/schema.go b/entc/integration/customid/ent/migrate/schema.go
index 5f0ff2dbd..6650bfbcc 100644
--- a/entc/integration/customid/ent/migrate/schema.go
+++ b/entc/integration/customid/ent/migrate/schema.go
@@ -37,6 +37,17 @@ var (
 		PrimaryKey:  []*schema.Column{GroupsColumns[0]},
 		ForeignKeys: []*schema.ForeignKey{},
 	}
+	// PetsColumns holds the columns for the "pets" table.
+	PetsColumns = []*schema.Column{
+		{Name: "id", Type: field.TypeString, Unique: true, Size: 25},
+	}
+	// PetsTable holds the schema information for the "pets" table.
+	PetsTable = &schema.Table{
+		Name:        "pets",
+		Columns:     PetsColumns,
+		PrimaryKey:  []*schema.Column{PetsColumns[0]},
+		ForeignKeys: []*schema.ForeignKey{},
+	}
 	// UsersColumns holds the columns for the "users" table.
 	UsersColumns = []*schema.Column{
 		{Name: "id", Type: field.TypeInt, Increment: true},
@@ -88,6 +99,7 @@ var (
 	Tables = []*schema.Table{
 		BlobsTable,
 		GroupsTable,
+		PetsTable,
 		UsersTable,
 		GroupUsersTable,
 	}
diff --git a/entc/integration/customid/ent/pet.go b/entc/integration/customid/ent/pet.go
new file mode 100644
index 000000000..9f10281a6
--- /dev/null
+++ b/entc/integration/customid/ent/pet.go
@@ -0,0 +1,87 @@
+// Copyright (c) Facebook, Inc. and its affiliates. 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 by entc, DO NOT EDIT.
+
+package ent
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/facebookincubator/ent/dialect/sql"
+	"github.com/facebookincubator/ent/entc/integration/customid/ent/pet"
+)
+
+// Pet is the model entity for the Pet schema.
+type Pet struct {
+	config
+	// ID of the ent.
+	ID string `json:"id,omitempty"`
+}
+
+// scanValues returns the types for scanning values from sql.Rows.
+func (*Pet) scanValues() []interface{} {
+	return []interface{}{
+		&sql.NullString{}, // id
+	}
+}
+
+// assignValues assigns the values that were returned from sql.Rows (after scanning)
+// to the Pet fields.
+func (pe *Pet) assignValues(values ...interface{}) error {
+	if m, n := len(values), len(pet.Columns); m < n {
+		return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
+	}
+	if value, ok := values[0].(*sql.NullString); !ok {
+		return fmt.Errorf("unexpected type %T for field id", values[0])
+	} else if value.Valid {
+		pe.ID = value.String
+	}
+	values = values[1:]
+	return nil
+}
+
+// Update returns a builder for updating this Pet.
+// Note that, you need to call Pet.Unwrap() before calling this method, if this Pet
+// was returned from a transaction, and the transaction was committed or rolled back.
+func (pe *Pet) Update() *PetUpdateOne {
+	return (&PetClient{pe.config}).UpdateOne(pe)
+}
+
+// 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 (pe *Pet) Unwrap() *Pet {
+	tx, ok := pe.config.driver.(*txDriver)
+	if !ok {
+		panic("ent: Pet is not a transactional entity")
+	}
+	pe.config.driver = tx.drv
+	return pe
+}
+
+// String implements the fmt.Stringer.
+func (pe *Pet) String() string {
+	var builder strings.Builder
+	builder.WriteString("Pet(")
+	builder.WriteString(fmt.Sprintf("id=%v", pe.ID))
+	builder.WriteByte(')')
+	return builder.String()
+}
+
+// id returns the int representation of the ID field.
+func (pe *Pet) id() int {
+	id, _ := strconv.Atoi(pe.ID)
+	return id
+}
+
+// Pets is a parsable slice of Pet.
+type Pets []*Pet
+
+func (pe Pets) config(cfg config) {
+	for _i := range pe {
+		pe[_i].config = cfg
+	}
+}
diff --git a/entc/integration/customid/ent/pet/pet.go b/entc/integration/customid/ent/pet/pet.go
new file mode 100644
index 000000000..d63ea09ec
--- /dev/null
+++ b/entc/integration/customid/ent/pet/pet.go
@@ -0,0 +1,22 @@
+// Copyright (c) Facebook, Inc. and its affiliates. 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 by entc, DO NOT EDIT.
+
+package pet
+
+const (
+	// Label holds the string label denoting the pet type in the database.
+	Label = "pet"
+	// FieldID holds the string denoting the id field in the database.
+	FieldID = "id"
+
+	// Table holds the table name of the pet in the database.
+	Table = "pets"
+)
+
+// Columns holds all SQL columns for pet fields.
+var Columns = []string{
+	FieldID,
+}
diff --git a/entc/integration/customid/ent/pet/where.go b/entc/integration/customid/ent/pet/where.go
new file mode 100644
index 000000000..711ed33e3
--- /dev/null
+++ b/entc/integration/customid/ent/pet/where.go
@@ -0,0 +1,127 @@
+// Copyright (c) Facebook, Inc. and its affiliates. 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 by entc, DO NOT EDIT.
+
+package pet
+
+import (
+	"github.com/facebookincubator/ent/dialect/sql"
+	"github.com/facebookincubator/ent/entc/integration/customid/ent/predicate"
+)
+
+// ID filters vertices based on their identifier.
+func ID(id string) predicate.Pet {
+	return predicate.Pet(func(s *sql.Selector) {
+		s.Where(sql.EQ(s.C(FieldID), id))
+	})
+}
+
+// IDEQ applies the EQ predicate on the ID field.
+func IDEQ(id string) predicate.Pet {
+	return predicate.Pet(func(s *sql.Selector) {
+		s.Where(sql.EQ(s.C(FieldID), id))
+	})
+}
+
+// IDNEQ applies the NEQ predicate on the ID field.
+func IDNEQ(id string) predicate.Pet {
+	return predicate.Pet(func(s *sql.Selector) {
+		s.Where(sql.NEQ(s.C(FieldID), id))
+	})
+}
+
+// IDIn applies the In predicate on the ID field.
+func IDIn(ids ...string) predicate.Pet {
+	return predicate.Pet(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 ...string) predicate.Pet {
+	return predicate.Pet(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...))
+	})
+}
+
+// IDGT applies the GT predicate on the ID field.
+func IDGT(id string) predicate.Pet {
+	return predicate.Pet(func(s *sql.Selector) {
+		s.Where(sql.GT(s.C(FieldID), id))
+	})
+}
+
+// IDGTE applies the GTE predicate on the ID field.
+func IDGTE(id string) predicate.Pet {
+	return predicate.Pet(func(s *sql.Selector) {
+		s.Where(sql.GTE(s.C(FieldID), id))
+	})
+}
+
+// IDLT applies the LT predicate on the ID field.
+func IDLT(id string) predicate.Pet {
+	return predicate.Pet(func(s *sql.Selector) {
+		s.Where(sql.LT(s.C(FieldID), id))
+	})
+}
+
+// IDLTE applies the LTE predicate on the ID field.
+func IDLTE(id string) predicate.Pet {
+	return predicate.Pet(func(s *sql.Selector) {
+		s.Where(sql.LTE(s.C(FieldID), id))
+	})
+}
+
+// And groups list of predicates with the AND operator between them.
+func And(predicates ...predicate.Pet) predicate.Pet {
+	return predicate.Pet(func(s *sql.Selector) {
+		s1 := s.Clone().SetP(nil)
+		for _, p := range predicates {
+			p(s1)
+		}
+		s.Where(s1.P())
+	})
+}
+
+// Or groups list of predicates with the OR operator between them.
+func Or(predicates ...predicate.Pet) predicate.Pet {
+	return predicate.Pet(func(s *sql.Selector) {
+		s1 := s.Clone().SetP(nil)
+		for i, p := range predicates {
+			if i > 0 {
+				s1.Or()
+			}
+			p(s1)
+		}
+		s.Where(s1.P())
+	})
+}
+
+// Not applies the not operator on the given predicate.
+func Not(p predicate.Pet) predicate.Pet {
+	return predicate.Pet(func(s *sql.Selector) {
+		p(s.Not())
+	})
+}
diff --git a/entc/integration/customid/ent/pet_create.go b/entc/integration/customid/ent/pet_create.go
new file mode 100644
index 000000000..781e66be6
--- /dev/null
+++ b/entc/integration/customid/ent/pet_create.go
@@ -0,0 +1,65 @@
+// Copyright (c) Facebook, Inc. and its affiliates. 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 by entc, DO NOT EDIT.
+
+package ent
+
+import (
+	"context"
+
+	"github.com/facebookincubator/ent/dialect/sql/sqlgraph"
+	"github.com/facebookincubator/ent/entc/integration/customid/ent/pet"
+	"github.com/facebookincubator/ent/schema/field"
+)
+
+// PetCreate is the builder for creating a Pet entity.
+type PetCreate struct {
+	config
+	id *string
+}
+
+// SetID sets the id field.
+func (pc *PetCreate) SetID(s string) *PetCreate {
+	pc.id = &s
+	return pc
+}
+
+// Save creates the Pet in the database.
+func (pc *PetCreate) Save(ctx context.Context) (*Pet, error) {
+	return pc.sqlSave(ctx)
+}
+
+// SaveX calls Save and panics if Save returns an error.
+func (pc *PetCreate) SaveX(ctx context.Context) *Pet {
+	v, err := pc.Save(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return v
+}
+
+func (pc *PetCreate) sqlSave(ctx context.Context) (*Pet, error) {
+	var (
+		pe    = &Pet{config: pc.config}
+		_spec = &sqlgraph.CreateSpec{
+			Table: pet.Table,
+			ID: &sqlgraph.FieldSpec{
+				Type:   field.TypeString,
+				Column: pet.FieldID,
+			},
+		}
+	)
+	if value := pc.id; value != nil {
+		pe.ID = *value
+		_spec.ID.Value = *value
+	}
+	if err := sqlgraph.CreateNode(ctx, pc.driver, _spec); err != nil {
+		if cerr, ok := isSQLConstraintError(err); ok {
+			err = cerr
+		}
+		return nil, err
+	}
+	return pe, nil
+}
diff --git a/entc/integration/customid/ent/pet_delete.go b/entc/integration/customid/ent/pet_delete.go
new file mode 100644
index 000000000..a2240589b
--- /dev/null
+++ b/entc/integration/customid/ent/pet_delete.go
@@ -0,0 +1,86 @@
+// Copyright (c) Facebook, Inc. and its affiliates. 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 by entc, DO NOT EDIT.
+
+package ent
+
+import (
+	"context"
+
+	"github.com/facebookincubator/ent/dialect/sql"
+	"github.com/facebookincubator/ent/dialect/sql/sqlgraph"
+	"github.com/facebookincubator/ent/entc/integration/customid/ent/pet"
+	"github.com/facebookincubator/ent/entc/integration/customid/ent/predicate"
+	"github.com/facebookincubator/ent/schema/field"
+)
+
+// PetDelete is the builder for deleting a Pet entity.
+type PetDelete struct {
+	config
+	predicates []predicate.Pet
+}
+
+// Where adds a new predicate to the delete builder.
+func (pd *PetDelete) Where(ps ...predicate.Pet) *PetDelete {
+	pd.predicates = append(pd.predicates, ps...)
+	return pd
+}
+
+// Exec executes the deletion query and returns how many vertices were deleted.
+func (pd *PetDelete) Exec(ctx context.Context) (int, error) {
+	return pd.sqlExec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (pd *PetDelete) ExecX(ctx context.Context) int {
+	n, err := pd.Exec(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return n
+}
+
+func (pd *PetDelete) sqlExec(ctx context.Context) (int, error) {
+	_spec := &sqlgraph.DeleteSpec{
+		Node: &sqlgraph.NodeSpec{
+			Table: pet.Table,
+			ID: &sqlgraph.FieldSpec{
+				Type:   field.TypeString,
+				Column: pet.FieldID,
+			},
+		},
+	}
+	if ps := pd.predicates; len(ps) > 0 {
+		_spec.Predicate = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	return sqlgraph.DeleteNodes(ctx, pd.driver, _spec)
+}
+
+// PetDeleteOne is the builder for deleting a single Pet entity.
+type PetDeleteOne struct {
+	pd *PetDelete
+}
+
+// Exec executes the deletion query.
+func (pdo *PetDeleteOne) Exec(ctx context.Context) error {
+	n, err := pdo.pd.Exec(ctx)
+	switch {
+	case err != nil:
+		return err
+	case n == 0:
+		return &NotFoundError{pet.Label}
+	default:
+		return nil
+	}
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (pdo *PetDeleteOne) ExecX(ctx context.Context) {
+	pdo.pd.ExecX(ctx)
+}
diff --git a/entc/integration/customid/ent/pet_query.go b/entc/integration/customid/ent/pet_query.go
new file mode 100644
index 000000000..7cb997db8
--- /dev/null
+++ b/entc/integration/customid/ent/pet_query.go
@@ -0,0 +1,594 @@
+// Copyright (c) Facebook, Inc. and its affiliates. 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 by entc, DO NOT EDIT.
+
+package ent
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"math"
+
+	"github.com/facebookincubator/ent/dialect/sql"
+	"github.com/facebookincubator/ent/dialect/sql/sqlgraph"
+	"github.com/facebookincubator/ent/entc/integration/customid/ent/pet"
+	"github.com/facebookincubator/ent/entc/integration/customid/ent/predicate"
+	"github.com/facebookincubator/ent/schema/field"
+)
+
+// PetQuery is the builder for querying Pet entities.
+type PetQuery struct {
+	config
+	limit      *int
+	offset     *int
+	order      []Order
+	unique     []string
+	predicates []predicate.Pet
+	// intermediate query.
+	sql *sql.Selector
+}
+
+// Where adds a new predicate for the builder.
+func (pq *PetQuery) Where(ps ...predicate.Pet) *PetQuery {
+	pq.predicates = append(pq.predicates, ps...)
+	return pq
+}
+
+// Limit adds a limit step to the query.
+func (pq *PetQuery) Limit(limit int) *PetQuery {
+	pq.limit = &limit
+	return pq
+}
+
+// Offset adds an offset step to the query.
+func (pq *PetQuery) Offset(offset int) *PetQuery {
+	pq.offset = &offset
+	return pq
+}
+
+// Order adds an order step to the query.
+func (pq *PetQuery) Order(o ...Order) *PetQuery {
+	pq.order = append(pq.order, o...)
+	return pq
+}
+
+// First returns the first Pet entity in the query. Returns *NotFoundError when no pet was found.
+func (pq *PetQuery) First(ctx context.Context) (*Pet, error) {
+	pes, err := pq.Limit(1).All(ctx)
+	if err != nil {
+		return nil, err
+	}
+	if len(pes) == 0 {
+		return nil, &NotFoundError{pet.Label}
+	}
+	return pes[0], nil
+}
+
+// FirstX is like First, but panics if an error occurs.
+func (pq *PetQuery) FirstX(ctx context.Context) *Pet {
+	pe, err := pq.First(ctx)
+	if err != nil && !IsNotFound(err) {
+		panic(err)
+	}
+	return pe
+}
+
+// FirstID returns the first Pet id in the query. Returns *NotFoundError when no id was found.
+func (pq *PetQuery) FirstID(ctx context.Context) (id string, err error) {
+	var ids []string
+	if ids, err = pq.Limit(1).IDs(ctx); err != nil {
+		return
+	}
+	if len(ids) == 0 {
+		err = &NotFoundError{pet.Label}
+		return
+	}
+	return ids[0], nil
+}
+
+// FirstXID is like FirstID, but panics if an error occurs.
+func (pq *PetQuery) FirstXID(ctx context.Context) string {
+	id, err := pq.FirstID(ctx)
+	if err != nil && !IsNotFound(err) {
+		panic(err)
+	}
+	return id
+}
+
+// Only returns the only Pet entity in the query, returns an error if not exactly one entity was returned.
+func (pq *PetQuery) Only(ctx context.Context) (*Pet, error) {
+	pes, err := pq.Limit(2).All(ctx)
+	if err != nil {
+		return nil, err
+	}
+	switch len(pes) {
+	case 1:
+		return pes[0], nil
+	case 0:
+		return nil, &NotFoundError{pet.Label}
+	default:
+		return nil, &NotSingularError{pet.Label}
+	}
+}
+
+// OnlyX is like Only, but panics if an error occurs.
+func (pq *PetQuery) OnlyX(ctx context.Context) *Pet {
+	pe, err := pq.Only(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return pe
+}
+
+// OnlyID returns the only Pet id in the query, returns an error if not exactly one id was returned.
+func (pq *PetQuery) OnlyID(ctx context.Context) (id string, err error) {
+	var ids []string
+	if ids, err = pq.Limit(2).IDs(ctx); err != nil {
+		return
+	}
+	switch len(ids) {
+	case 1:
+		id = ids[0]
+	case 0:
+		err = &NotFoundError{pet.Label}
+	default:
+		err = &NotSingularError{pet.Label}
+	}
+	return
+}
+
+// OnlyXID is like OnlyID, but panics if an error occurs.
+func (pq *PetQuery) OnlyXID(ctx context.Context) string {
+	id, err := pq.OnlyID(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return id
+}
+
+// All executes the query and returns a list of Pets.
+func (pq *PetQuery) All(ctx context.Context) ([]*Pet, error) {
+	return pq.sqlAll(ctx)
+}
+
+// AllX is like All, but panics if an error occurs.
+func (pq *PetQuery) AllX(ctx context.Context) []*Pet {
+	pes, err := pq.All(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return pes
+}
+
+// IDs executes the query and returns a list of Pet ids.
+func (pq *PetQuery) IDs(ctx context.Context) ([]string, error) {
+	var ids []string
+	if err := pq.Select(pet.FieldID).Scan(ctx, &ids); err != nil {
+		return nil, err
+	}
+	return ids, nil
+}
+
+// IDsX is like IDs, but panics if an error occurs.
+func (pq *PetQuery) IDsX(ctx context.Context) []string {
+	ids, err := pq.IDs(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return ids
+}
+
+// Count returns the count of the given query.
+func (pq *PetQuery) Count(ctx context.Context) (int, error) {
+	return pq.sqlCount(ctx)
+}
+
+// CountX is like Count, but panics if an error occurs.
+func (pq *PetQuery) CountX(ctx context.Context) int {
+	count, err := pq.Count(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return count
+}
+
+// Exist returns true if the query has elements in the graph.
+func (pq *PetQuery) Exist(ctx context.Context) (bool, error) {
+	return pq.sqlExist(ctx)
+}
+
+// ExistX is like Exist, but panics if an error occurs.
+func (pq *PetQuery) ExistX(ctx context.Context) bool {
+	exist, err := pq.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 (pq *PetQuery) Clone() *PetQuery {
+	return &PetQuery{
+		config:     pq.config,
+		limit:      pq.limit,
+		offset:     pq.offset,
+		order:      append([]Order{}, pq.order...),
+		unique:     append([]string{}, pq.unique...),
+		predicates: append([]predicate.Pet{}, pq.predicates...),
+		// clone intermediate query.
+		sql: pq.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.
+func (pq *PetQuery) GroupBy(field string, fields ...string) *PetGroupBy {
+	group := &PetGroupBy{config: pq.config}
+	group.fields = append([]string{field}, fields...)
+	group.sql = pq.sqlQuery()
+	return group
+}
+
+// Select one or more fields from the given query.
+func (pq *PetQuery) Select(field string, fields ...string) *PetSelect {
+	selector := &PetSelect{config: pq.config}
+	selector.fields = append([]string{field}, fields...)
+	selector.sql = pq.sqlQuery()
+	return selector
+}
+
+func (pq *PetQuery) sqlAll(ctx context.Context) ([]*Pet, error) {
+	var (
+		nodes = []*Pet{}
+		_spec = pq.querySpec()
+	)
+	_spec.ScanValues = func() []interface{} {
+		node := &Pet{config: pq.config}
+		nodes = append(nodes, node)
+		values := node.scanValues()
+		return values
+	}
+	_spec.Assign = func(values ...interface{}) error {
+		if len(nodes) == 0 {
+			return fmt.Errorf("ent: Assign called without calling ScanValues")
+		}
+		node := nodes[len(nodes)-1]
+		return node.assignValues(values...)
+	}
+	if err := sqlgraph.QueryNodes(ctx, pq.driver, _spec); err != nil {
+		return nil, err
+	}
+	if len(nodes) == 0 {
+		return nodes, nil
+	}
+	return nodes, nil
+}
+
+func (pq *PetQuery) sqlCount(ctx context.Context) (int, error) {
+	_spec := pq.querySpec()
+	return sqlgraph.CountNodes(ctx, pq.driver, _spec)
+}
+
+func (pq *PetQuery) sqlExist(ctx context.Context) (bool, error) {
+	n, err := pq.sqlCount(ctx)
+	if err != nil {
+		return false, fmt.Errorf("ent: check existence: %v", err)
+	}
+	return n > 0, nil
+}
+
+func (pq *PetQuery) querySpec() *sqlgraph.QuerySpec {
+	_spec := &sqlgraph.QuerySpec{
+		Node: &sqlgraph.NodeSpec{
+			Table:   pet.Table,
+			Columns: pet.Columns,
+			ID: &sqlgraph.FieldSpec{
+				Type:   field.TypeString,
+				Column: pet.FieldID,
+			},
+		},
+		From:   pq.sql,
+		Unique: true,
+	}
+	if ps := pq.predicates; len(ps) > 0 {
+		_spec.Predicate = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	if limit := pq.limit; limit != nil {
+		_spec.Limit = *limit
+	}
+	if offset := pq.offset; offset != nil {
+		_spec.Offset = *offset
+	}
+	if ps := pq.order; len(ps) > 0 {
+		_spec.Order = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	return _spec
+}
+
+func (pq *PetQuery) sqlQuery() *sql.Selector {
+	builder := sql.Dialect(pq.driver.Dialect())
+	t1 := builder.Table(pet.Table)
+	selector := builder.Select(t1.Columns(pet.Columns...)...).From(t1)
+	if pq.sql != nil {
+		selector = pq.sql
+		selector.Select(selector.Columns(pet.Columns...)...)
+	}
+	for _, p := range pq.predicates {
+		p(selector)
+	}
+	for _, p := range pq.order {
+		p(selector)
+	}
+	if offset := pq.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.MaxInt32)
+	}
+	if limit := pq.limit; limit != nil {
+		selector.Limit(*limit)
+	}
+	return selector
+}
+
+// PetGroupBy is the builder for group-by Pet entities.
+type PetGroupBy struct {
+	config
+	fields []string
+	fns    []Aggregate
+	// intermediate query.
+	sql *sql.Selector
+}
+
+// Aggregate adds the given aggregation functions to the group-by query.
+func (pgb *PetGroupBy) Aggregate(fns ...Aggregate) *PetGroupBy {
+	pgb.fns = append(pgb.fns, fns...)
+	return pgb
+}
+
+// Scan applies the group-by query and scan the result into the given value.
+func (pgb *PetGroupBy) Scan(ctx context.Context, v interface{}) error {
+	return pgb.sqlScan(ctx, v)
+}
+
+// ScanX is like Scan, but panics if an error occurs.
+func (pgb *PetGroupBy) ScanX(ctx context.Context, v interface{}) {
+	if err := pgb.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 (pgb *PetGroupBy) Strings(ctx context.Context) ([]string, error) {
+	if len(pgb.fields) > 1 {
+		return nil, errors.New("ent: PetGroupBy.Strings is not achievable when grouping more than 1 field")
+	}
+	var v []string
+	if err := pgb.Scan(ctx, &v); err != nil {
+		return nil, err
+	}
+	return v, nil
+}
+
+// StringsX is like Strings, but panics if an error occurs.
+func (pgb *PetGroupBy) StringsX(ctx context.Context) []string {
+	v, err := pgb.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 (pgb *PetGroupBy) Ints(ctx context.Context) ([]int, error) {
+	if len(pgb.fields) > 1 {
+		return nil, errors.New("ent: PetGroupBy.Ints is not achievable when grouping more than 1 field")
+	}
+	var v []int
+	if err := pgb.Scan(ctx, &v); err != nil {
+		return nil, err
+	}
+	return v, nil
+}
+
+// IntsX is like Ints, but panics if an error occurs.
+func (pgb *PetGroupBy) IntsX(ctx context.Context) []int {
+	v, err := pgb.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 (pgb *PetGroupBy) Float64s(ctx context.Context) ([]float64, error) {
+	if len(pgb.fields) > 1 {
+		return nil, errors.New("ent: PetGroupBy.Float64s is not achievable when grouping more than 1 field")
+	}
+	var v []float64
+	if err := pgb.Scan(ctx, &v); err != nil {
+		return nil, err
+	}
+	return v, nil
+}
+
+// Float64sX is like Float64s, but panics if an error occurs.
+func (pgb *PetGroupBy) Float64sX(ctx context.Context) []float64 {
+	v, err := pgb.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 (pgb *PetGroupBy) Bools(ctx context.Context) ([]bool, error) {
+	if len(pgb.fields) > 1 {
+		return nil, errors.New("ent: PetGroupBy.Bools is not achievable when grouping more than 1 field")
+	}
+	var v []bool
+	if err := pgb.Scan(ctx, &v); err != nil {
+		return nil, err
+	}
+	return v, nil
+}
+
+// BoolsX is like Bools, but panics if an error occurs.
+func (pgb *PetGroupBy) BoolsX(ctx context.Context) []bool {
+	v, err := pgb.Bools(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return v
+}
+
+func (pgb *PetGroupBy) sqlScan(ctx context.Context, v interface{}) error {
+	rows := &sql.Rows{}
+	query, args := pgb.sqlQuery().Query()
+	if err := pgb.driver.Query(ctx, query, args, rows); err != nil {
+		return err
+	}
+	defer rows.Close()
+	return sql.ScanSlice(rows, v)
+}
+
+func (pgb *PetGroupBy) sqlQuery() *sql.Selector {
+	selector := pgb.sql
+	columns := make([]string, 0, len(pgb.fields)+len(pgb.fns))
+	columns = append(columns, pgb.fields...)
+	for _, fn := range pgb.fns {
+		columns = append(columns, fn(selector))
+	}
+	return selector.Select(columns...).GroupBy(pgb.fields...)
+}
+
+// PetSelect is the builder for select fields of Pet entities.
+type PetSelect struct {
+	config
+	fields []string
+	// intermediate queries.
+	sql *sql.Selector
+}
+
+// Scan applies the selector query and scan the result into the given value.
+func (ps *PetSelect) Scan(ctx context.Context, v interface{}) error {
+	return ps.sqlScan(ctx, v)
+}
+
+// ScanX is like Scan, but panics if an error occurs.
+func (ps *PetSelect) ScanX(ctx context.Context, v interface{}) {
+	if err := ps.Scan(ctx, v); err != nil {
+		panic(err)
+	}
+}
+
+// Strings returns list of strings from selector. It is only allowed when selecting one field.
+func (ps *PetSelect) Strings(ctx context.Context) ([]string, error) {
+	if len(ps.fields) > 1 {
+		return nil, errors.New("ent: PetSelect.Strings is not achievable when selecting more than 1 field")
+	}
+	var v []string
+	if err := ps.Scan(ctx, &v); err != nil {
+		return nil, err
+	}
+	return v, nil
+}
+
+// StringsX is like Strings, but panics if an error occurs.
+func (ps *PetSelect) StringsX(ctx context.Context) []string {
+	v, err := ps.Strings(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return v
+}
+
+// Ints returns list of ints from selector. It is only allowed when selecting one field.
+func (ps *PetSelect) Ints(ctx context.Context) ([]int, error) {
+	if len(ps.fields) > 1 {
+		return nil, errors.New("ent: PetSelect.Ints is not achievable when selecting more than 1 field")
+	}
+	var v []int
+	if err := ps.Scan(ctx, &v); err != nil {
+		return nil, err
+	}
+	return v, nil
+}
+
+// IntsX is like Ints, but panics if an error occurs.
+func (ps *PetSelect) IntsX(ctx context.Context) []int {
+	v, err := ps.Ints(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return v
+}
+
+// Float64s returns list of float64s from selector. It is only allowed when selecting one field.
+func (ps *PetSelect) Float64s(ctx context.Context) ([]float64, error) {
+	if len(ps.fields) > 1 {
+		return nil, errors.New("ent: PetSelect.Float64s is not achievable when selecting more than 1 field")
+	}
+	var v []float64
+	if err := ps.Scan(ctx, &v); err != nil {
+		return nil, err
+	}
+	return v, nil
+}
+
+// Float64sX is like Float64s, but panics if an error occurs.
+func (ps *PetSelect) Float64sX(ctx context.Context) []float64 {
+	v, err := ps.Float64s(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return v
+}
+
+// Bools returns list of bools from selector. It is only allowed when selecting one field.
+func (ps *PetSelect) Bools(ctx context.Context) ([]bool, error) {
+	if len(ps.fields) > 1 {
+		return nil, errors.New("ent: PetSelect.Bools is not achievable when selecting more than 1 field")
+	}
+	var v []bool
+	if err := ps.Scan(ctx, &v); err != nil {
+		return nil, err
+	}
+	return v, nil
+}
+
+// BoolsX is like Bools, but panics if an error occurs.
+func (ps *PetSelect) BoolsX(ctx context.Context) []bool {
+	v, err := ps.Bools(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return v
+}
+
+func (ps *PetSelect) sqlScan(ctx context.Context, v interface{}) error {
+	rows := &sql.Rows{}
+	query, args := ps.sqlQuery().Query()
+	if err := ps.driver.Query(ctx, query, args, rows); err != nil {
+		return err
+	}
+	defer rows.Close()
+	return sql.ScanSlice(rows, v)
+}
+
+func (ps *PetSelect) sqlQuery() sql.Querier {
+	selector := ps.sql
+	selector.Select(selector.Columns(ps.fields...)...)
+	return selector
+}
diff --git a/entc/integration/customid/ent/pet_update.go b/entc/integration/customid/ent/pet_update.go
new file mode 100644
index 000000000..192a16d3e
--- /dev/null
+++ b/entc/integration/customid/ent/pet_update.go
@@ -0,0 +1,140 @@
+// Copyright (c) Facebook, Inc. and its affiliates. 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 by entc, DO NOT EDIT.
+
+package ent
+
+import (
+	"context"
+
+	"github.com/facebookincubator/ent/dialect/sql"
+	"github.com/facebookincubator/ent/dialect/sql/sqlgraph"
+	"github.com/facebookincubator/ent/entc/integration/customid/ent/pet"
+	"github.com/facebookincubator/ent/entc/integration/customid/ent/predicate"
+	"github.com/facebookincubator/ent/schema/field"
+)
+
+// PetUpdate is the builder for updating Pet entities.
+type PetUpdate struct {
+	config
+	predicates []predicate.Pet
+}
+
+// Where adds a new predicate for the builder.
+func (pu *PetUpdate) Where(ps ...predicate.Pet) *PetUpdate {
+	pu.predicates = append(pu.predicates, ps...)
+	return pu
+}
+
+// Save executes the query and returns the number of rows/vertices matched by this operation.
+func (pu *PetUpdate) Save(ctx context.Context) (int, error) {
+	return pu.sqlSave(ctx)
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (pu *PetUpdate) SaveX(ctx context.Context) int {
+	affected, err := pu.Save(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return affected
+}
+
+// Exec executes the query.
+func (pu *PetUpdate) Exec(ctx context.Context) error {
+	_, err := pu.Save(ctx)
+	return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (pu *PetUpdate) ExecX(ctx context.Context) {
+	if err := pu.Exec(ctx); err != nil {
+		panic(err)
+	}
+}
+
+func (pu *PetUpdate) sqlSave(ctx context.Context) (n int, err error) {
+	_spec := &sqlgraph.UpdateSpec{
+		Node: &sqlgraph.NodeSpec{
+			Table:   pet.Table,
+			Columns: pet.Columns,
+			ID: &sqlgraph.FieldSpec{
+				Type:   field.TypeString,
+				Column: pet.FieldID,
+			},
+		},
+	}
+	if ps := pu.predicates; len(ps) > 0 {
+		_spec.Predicate = func(selector *sql.Selector) {
+			for i := range ps {
+				ps[i](selector)
+			}
+		}
+	}
+	if n, err = sqlgraph.UpdateNodes(ctx, pu.driver, _spec); err != nil {
+		if cerr, ok := isSQLConstraintError(err); ok {
+			err = cerr
+		}
+		return 0, err
+	}
+	return n, nil
+}
+
+// PetUpdateOne is the builder for updating a single Pet entity.
+type PetUpdateOne struct {
+	config
+	id string
+}
+
+// Save executes the query and returns the updated entity.
+func (puo *PetUpdateOne) Save(ctx context.Context) (*Pet, error) {
+	return puo.sqlSave(ctx)
+}
+
+// SaveX is like Save, but panics if an error occurs.
+func (puo *PetUpdateOne) SaveX(ctx context.Context) *Pet {
+	pe, err := puo.Save(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return pe
+}
+
+// Exec executes the query on the entity.
+func (puo *PetUpdateOne) Exec(ctx context.Context) error {
+	_, err := puo.Save(ctx)
+	return err
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (puo *PetUpdateOne) ExecX(ctx context.Context) {
+	if err := puo.Exec(ctx); err != nil {
+		panic(err)
+	}
+}
+
+func (puo *PetUpdateOne) sqlSave(ctx context.Context) (pe *Pet, err error) {
+	_spec := &sqlgraph.UpdateSpec{
+		Node: &sqlgraph.NodeSpec{
+			Table:   pet.Table,
+			Columns: pet.Columns,
+			ID: &sqlgraph.FieldSpec{
+				Value:  puo.id,
+				Type:   field.TypeString,
+				Column: pet.FieldID,
+			},
+		},
+	}
+	pe = &Pet{config: puo.config}
+	_spec.Assign = pe.assignValues
+	_spec.ScanValues = pe.scanValues()
+	if err = sqlgraph.UpdateNode(ctx, puo.driver, _spec); err != nil {
+		if cerr, ok := isSQLConstraintError(err); ok {
+			err = cerr
+		}
+		return nil, err
+	}
+	return pe, nil
+}
diff --git a/entc/integration/customid/ent/predicate/predicate.go b/entc/integration/customid/ent/predicate/predicate.go
index 74f39f0b3..2054453ef 100644
--- a/entc/integration/customid/ent/predicate/predicate.go
+++ b/entc/integration/customid/ent/predicate/predicate.go
@@ -16,5 +16,8 @@ type Blob func(*sql.Selector)
 // Group is the predicate function for group builders.
 type Group func(*sql.Selector)
 
+// Pet is the predicate function for pet builders.
+type Pet func(*sql.Selector)
+
 // User is the predicate function for user builders.
 type User func(*sql.Selector)
diff --git a/entc/integration/customid/ent/schema/pet.go b/entc/integration/customid/ent/schema/pet.go
new file mode 100644
index 000000000..df214ffbc
--- /dev/null
+++ b/entc/integration/customid/ent/schema/pet.go
@@ -0,0 +1,26 @@
+// 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/field"
+)
+
+// Pet holds the schema definition for the Pet entity.
+type Pet struct {
+	ent.Schema
+}
+
+// Fields of the Pet.
+func (Pet) Fields() []ent.Field {
+	return []ent.Field{
+		field.String("id").
+			MaxLen(25).
+			NotEmpty().
+			Unique().
+			Immutable(),
+	}
+}
diff --git a/entc/integration/customid/ent/tx.go b/entc/integration/customid/ent/tx.go
index f8f0055f3..e27d14e36 100644
--- a/entc/integration/customid/ent/tx.go
+++ b/entc/integration/customid/ent/tx.go
@@ -20,6 +20,8 @@ type Tx struct {
 	Blob *BlobClient
 	// Group is the client for interacting with the Group builders.
 	Group *GroupClient
+	// Pet is the client for interacting with the Pet builders.
+	Pet *PetClient
 	// User is the client for interacting with the User builders.
 	User *UserClient
 }
@@ -41,6 +43,7 @@ func (tx *Tx) Client() *Client {
 		Schema: migrate.NewSchema(tx.driver),
 		Blob:   NewBlobClient(tx.config),
 		Group:  NewGroupClient(tx.config),
+		Pet:    NewPetClient(tx.config),
 		User:   NewUserClient(tx.config),
 	}
 }



Test patch:
diff --git a/entc/integration/customid/ent/example_test.go b/entc/integration/customid/ent/example_test.go
index 1bc4d8621..d6c77f93a 100644
--- a/entc/integration/customid/ent/example_test.go
+++ b/entc/integration/customid/ent/example_test.go
@@ -77,6 +77,29 @@ func ExampleGroup() {
 
 	// Output:
 }
+func ExamplePet() {
+	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 pet's edges.
+
+	// create pet vertex with its edges.
+	pe := client.Pet.
+		Create().
+		SaveX(ctx)
+	log.Println("pet created:", pe)
+
+	// query edges.
+
+	// Output:
+}
 func ExampleUser() {
 	if dsn == "" {
 		return