diff --git a/entc/gen/template/builder/setter.tmpl b/entc/gen/template/builder/setter.tmpl index eb021b7a1..cf31013ef 100644 --- a/entc/gen/template/builder/setter.tmpl +++ b/entc/gen/template/builder/setter.tmpl @@ -78,7 +78,7 @@ in the LICENSE file in the root directory of this source tree. {{- range $f := $.BlobFields }} {{ $func := print "Set" $f.StructField }} // {{ $func }} sets the "{{ $f.Name }}" field. - func ({{ $receiver }} *{{ $builder }}) {{ $func }}(v []byte) *{{ $builder }} { + func ({{ $receiver }} *{{ $builder }}) {{ $func }}(v io.Reader) *{{ $builder }} { {{ $receiver }}.mutation.Set{{ $f.StructField }}(v) return {{ $receiver }} } diff --git a/entc/gen/template/dialect/sql/create.tmpl b/entc/gen/template/dialect/sql/create.tmpl index 8a625de54..1d6f2a3a6 100644 --- a/entc/gen/template/dialect/sql/create.tmpl +++ b/entc/gen/template/dialect/sql/create.tmpl @@ -79,7 +79,7 @@ func ({{ $receiver }} *{{ $builder }}) sqlSave(ctx context.Context) (*{{ $.Name {{- end }} {{- if $.HasBlobFields }} {{- range $f := $.BlobFields }} - if data, ok := {{ $mutation }}.{{ $f.StructField }}(); ok { + if r, ok := {{ $mutation }}.{{ $f.StructField }}(); ok { b, err := {{ $mutation }}.blobOpeners.{{ $.Name }}(ctx, {{ $.Package }}.{{ $f.Constant }}) if err != nil { return nil, fmt.Errorf("{{ $pkg }}: opening blob bucket for {{ $f.Name }}: %w", err) @@ -92,7 +92,7 @@ func ({{ $receiver }} *{{ $builder }}) sqlSave(ctx context.Context) (*{{ $.Name if err != nil { return nil, errors.Join(fmt.Errorf("{{ $pkg }}: creating writer for {{ $f.Name }}: %w", err), b.Close()) } - if _, err := w.Write(data); err != nil { + if _, err := io.Copy(w, r); err != nil { return nil, errors.Join(fmt.Errorf("{{ $pkg }}: writing blob for {{ $f.Name }}: %w", err), w.Close(), b.Close()) } if err := errors.Join(w.Close(), b.Close()); err != nil { @@ -289,7 +289,7 @@ func ({{ $receiver }} *{{ $builder }}) Save(ctx context.Context) ([]*{{ $.Name } {{- end }} mutation.done = true {{- range $f := $.BlobFields }} - if data, ok := mutation.{{ $f.StructField }}(); ok { + if r, ok := mutation.{{ $f.StructField }}(); ok { if _blob{{ $f.StructField }} == nil { if _blob{{ $f.StructField }}, err = mutation.blobOpeners.{{ $.Name }}(ctx, {{ $.Package }}.{{ $f.Constant }}); err != nil { return nil, fmt.Errorf("{{ $pkg }}: opening blob bucket for {{ $f.Name }}: %w", err) @@ -303,7 +303,7 @@ func ({{ $receiver }} *{{ $builder }}) Save(ctx context.Context) ([]*{{ $.Name } if err != nil { return nil, fmt.Errorf("{{ $pkg }}: creating writer for {{ $f.Name }}: %w", err) } - if _, err := w.Write(data); err != nil { + if _, err := io.Copy(w, r); err != nil { return nil, errors.Join(fmt.Errorf("{{ $pkg }}: writing blob for {{ $f.Name }}: %w", err), w.Close()) } if err := w.Close(); err != nil { diff --git a/entc/gen/template/dialect/sql/update.tmpl b/entc/gen/template/dialect/sql/update.tmpl index d4823975d..3f8e180f6 100644 --- a/entc/gen/template/dialect/sql/update.tmpl +++ b/entc/gen/template/dialect/sql/update.tmpl @@ -189,7 +189,7 @@ func ({{ $receiver }} *{{ $builder }}) sqlSave(ctx context.Context) (_node {{ if {{ $mutation }}.done = true {{- if and $one $.HasBlobFields }} {{- range $f := $.BlobFields }} - if data, ok := {{ $mutation }}.{{ $f.StructField }}(); ok { + if r, ok := {{ $mutation }}.{{ $f.StructField }}(); ok { b, err := {{ $mutation }}.blobOpeners.{{ $.Name }}(ctx, {{ $.Package }}.{{ $f.Constant }}) if err != nil { return nil, fmt.Errorf("{{ $pkg }}: opening blob bucket for {{ $f.Name }}: %w", err) @@ -202,7 +202,7 @@ func ({{ $receiver }} *{{ $builder }}) sqlSave(ctx context.Context) (_node {{ if if err != nil { return nil, errors.Join(fmt.Errorf("{{ $pkg }}: creating writer for {{ $f.Name }}: %w", err), b.Close()) } - if _, err := w.Write(data); err != nil { + if _, err := io.Copy(w, r); err != nil { return nil, errors.Join(fmt.Errorf("{{ $pkg }}: writing blob for {{ $f.Name }}: %w", err), w.Close(), b.Close()) } if err := errors.Join(w.Close(), b.Close()); err != nil { diff --git a/entc/gen/template/ent.tmpl b/entc/gen/template/ent.tmpl index 6002be2b9..4f2bc53fd 100644 --- a/entc/gen/template/ent.tmpl +++ b/entc/gen/template/ent.tmpl @@ -102,23 +102,10 @@ type {{ $edgesType }} struct { {{ end }} {{- range $f := $.BlobFields }} - // {{ $f.StructField }} reads the blob data for the "{{ $f.Name }}" field from blob storage. - // It returns nil, nil if the blob does not exist. - func ({{ $receiver }} *{{ $.Name }}) {{ $f.StructField }}(ctx context.Context) ([]byte, error) { - switch r, err := {{ $receiver }}.Open{{ $f.StructField }}Reader(ctx); { - case errors.Is(err, fs.ErrNotExist): - return nil, nil - case err != nil: - return nil, err - default: - defer r.Close() - return io.ReadAll(r) - } - } - - // Open{{ $f.StructField }}Reader opens a reader for the "{{ $f.Name }}" field. + // {{ $f.StructField }} opens a reader for the "{{ $f.Name }}" field from blob storage. // The caller must close the returned reader when done. - func ({{ $receiver }} *{{ $.Name }}) Open{{ $f.StructField }}Reader(ctx context.Context) (io.ReadCloser, error) { + // It returns nil, nil if the blob does not exist. + func ({{ $receiver }} *{{ $.Name }}) {{ $f.StructField }}(ctx context.Context) (io.ReadCloser, error) { key, err := {{ $receiver }}.{{ $f.StructField }}Key(ctx) if err != nil { return nil, fmt.Errorf("{{ $pkg }}: blob key for {{ $f.Name }}: %w", err) @@ -127,17 +114,20 @@ type {{ $edgesType }} struct { if err != nil { return nil, fmt.Errorf("{{ $pkg }}: opening blob bucket for {{ $f.Name }}: %w", err) } - r, err := b.NewReader(ctx, key) - if err != nil { + switch r, err := b.NewReader(ctx, key); { + case errors.Is(err, fs.ErrNotExist): + return nil, b.Close() + case err != nil: return nil, errors.Join(fmt.Errorf("{{ $pkg }}: creating reader for {{ $f.Name }}: %w", err), b.Close()) + default: + return r, nil } - return r, nil } - // Open{{ $f.StructField }}Writer opens a writer for the "{{ $f.Name }}" field. + // {{ $f.StructField }}Writer opens a writer for the "{{ $f.Name }}" field in blob storage. // The caller must close the returned writer when done. // Writing via this method does not go through the mutation pipeline. - func ({{ $receiver }} *{{ $.Name }}) Open{{ $f.StructField }}Writer(ctx context.Context) (io.WriteCloser, error) { + func ({{ $receiver }} *{{ $.Name }}) {{ $f.StructField }}Writer(ctx context.Context) (io.WriteCloser, error) { key, err := {{ $receiver }}.{{ $f.StructField }}Key(ctx) if err != nil { return nil, fmt.Errorf("{{ $pkg }}: blob key for {{ $f.Name }}: %w", err) diff --git a/entc/gen/template/mutation.tmpl b/entc/gen/template/mutation.tmpl index c906b11ed..05f9da279 100644 --- a/entc/gen/template/mutation.tmpl +++ b/entc/gen/template/mutation.tmpl @@ -33,7 +33,7 @@ type Mutation struct { {{- end }} {{- end }} {{- range $f := $.BlobFields }} - {{ $f.BuilderField }} *[]byte + {{ $f.BuilderField }} io.Reader {{- end }} clearedFields map[string]struct{} {{- range $e := $.EdgesWithID }} @@ -174,17 +174,17 @@ func (m *Mutation) Predicates() []predicate.{{ $.Name }} { {{ $const := $f.Constant }} {{ $func := print "Set" $f.StructField }} // {{ $func }} sets the "{{ $f.Name }}" field. - func (m *Mutation) {{ $func }}(b []byte) { - m.{{ $f.BuilderField }} = &b + func (m *Mutation) {{ $func }}(r io.Reader) { + m.{{ $f.BuilderField }} = r } // {{ $f.StructField }} returns the value of the "{{ $f.Name }}" field in the mutation. - func (m *Mutation) {{ $f.StructField }}() (r []byte, exists bool) { + func (m *Mutation) {{ $f.StructField }}() (r io.Reader, exists bool) { v := m.{{ $f.BuilderField }} if v == nil { return } - return *v, true + return v, true } {{- if $f.Optional }} diff --git a/entc/gen/type.go b/entc/gen/type.go index c17cf1cf8..e5aa7064f 100644 --- a/entc/gen/type.go +++ b/entc/gen/type.go @@ -1356,7 +1356,7 @@ func (f Field) IsEnum() bool { return f.Type != nil && f.Type.Type == field.Type // IsBlob reports whether this field is stored in external blob storage. func (f Field) IsBlob() bool { - return f.def != nil && f.def.Blob + return f.Type != nil && f.Type.Type == field.TypeBlob } // IsEdgeField reports if the given field is an edge-field (i.e. a foreign-key) diff --git a/entc/gen/type_test.go b/entc/gen/type_test.go index 5860b5e58..984f8fba5 100644 --- a/entc/gen/type_test.go +++ b/entc/gen/type_test.go @@ -346,16 +346,14 @@ func TestField_Blob(t *testing.T) { Name: "Document", Fields: []*load.Field{ { - Name: "content", - Info: &field.TypeInfo{Type: field.TypeBytes}, - Optional: true, - Comment: "blob content", - Blob: true, + Name: "content", + Info: &field.TypeInfo{Type: field.TypeBlob}, + Optional: true, + Comment: "blob content", }, { - Name: "thumbnail", - Info: &field.TypeInfo{Type: field.TypeBytes}, - Blob: true, + Name: "thumbnail", + Info: &field.TypeInfo{Type: field.TypeBlob}, }, { Name: "title", @@ -413,9 +411,8 @@ func TestField_BlobScanType(t *testing.T) { Name: "Doc", Fields: []*load.Field{ { - Name: "data", - Info: &field.TypeInfo{Type: field.TypeBytes}, - Blob: true, + Name: "data", + Info: &field.TypeInfo{Type: field.TypeBlob}, }, }, }) diff --git a/entc/integration/blob_test.go b/entc/integration/blob_test.go index ebe5578ef..f014e50c6 100644 --- a/entc/integration/blob_test.go +++ b/entc/integration/blob_test.go @@ -5,6 +5,7 @@ package integration import ( + "bytes" "context" "crypto/sha256" "encoding/hex" @@ -27,6 +28,26 @@ import ( _ "gocloud.dev/blob/fileblob" ) +// readBlob reads all data from a blob field reader and closes it. +func readBlob(t *testing.T, rc io.ReadCloser, err error) []byte { + t.Helper() + require.NoError(t, err) + if rc == nil { + return nil + } + data, readErr := io.ReadAll(rc) + require.NoError(t, readErr) + require.NoError(t, rc.Close()) + return data +} + +// blobContent is a shorthand for readBlob(t, entity.Field(ctx)). +func blobContent(t *testing.T, fn func(context.Context) (io.ReadCloser, error), ctx context.Context) []byte { + t.Helper() + rc, err := fn(ctx) + return readBlob(t, rc, err) +} + // blobDir creates a temp directory with subdirectories for blob fields. func blobDir(t *testing.T) string { t.Helper() @@ -78,13 +99,12 @@ func TestBlobCreateAndRead(t *testing.T) { data := []byte("Hello from blob integration test!") doc := client.Document.Create(). SetName("test-doc"). - SetContent(data). - SetThumbnail([]byte("thumb")). + SetContent(bytes.NewReader(data)). + SetThumbnail(bytes.NewReader([]byte("thumb"))). SaveX(ctx) // Read the blob back through the entity method. - got, err := doc.Content(ctx) - require.NoError(t, err) + got := blobContent(t, doc.Content, ctx) require.Equal(t, data, got) } @@ -94,16 +114,15 @@ func TestBlobQueryAndRead(t *testing.T) { data := []byte("queried blob content") created := client.Document.Create(). SetName("query-doc"). - SetContent(data). - SetThumbnail([]byte("thumb")). + SetContent(bytes.NewReader(data)). + SetThumbnail(bytes.NewReader([]byte("thumb"))). SaveX(ctx) // Query it back from the database (no content column, just ID/name). queried := client.Document.GetX(ctx, created.ID) // Read blob content from the queried entity. - got, err := queried.Content(ctx) - require.NoError(t, err) + got := blobContent(t, queried.Content, ctx) require.Equal(t, data, got) } @@ -113,19 +132,18 @@ func TestBlobUpdateData(t *testing.T) { v1 := []byte("version 1") doc := client.Document.Create(). SetName("update-doc"). - SetContent(v1). - SetThumbnail([]byte("thumb")). + SetContent(bytes.NewReader(v1)). + SetThumbnail(bytes.NewReader([]byte("thumb"))). SaveX(ctx) // Update blob data through mutation (overwrites same key). v2 := []byte("version 2 - updated via mutation") doc = doc.Update(). - SetContent(v2). + SetContent(bytes.NewReader(v2)). SaveX(ctx) // Read the new blob content. - got, err := doc.Content(ctx) - require.NoError(t, err) + got := blobContent(t, doc.Content, ctx) require.Equal(t, v2, got) } @@ -153,8 +171,8 @@ func TestBlobMultipleDocuments(t *testing.T) { for i, c := range contents { doc := client.Document.Create(). SetName("multi-" + string(rune('a'+i))). - SetContent([]byte(c)). - SetThumbnail([]byte("thumb")). + SetContent(strings.NewReader(c)). + SetThumbnail(bytes.NewReader([]byte("thumb"))). SaveX(ctx) ids = append(ids, doc.ID) } @@ -162,8 +180,7 @@ func TestBlobMultipleDocuments(t *testing.T) { // Read each document and verify content is correct. for i, id := range ids { doc := client.Document.GetX(ctx, id) - got, err := doc.Content(ctx) - require.NoError(t, err) + got := blobContent(t, doc.Content, ctx) require.Equal(t, contents[i], string(got), "document %d content mismatch", i) } } @@ -175,8 +192,8 @@ func TestBlobBulkCreate(t *testing.T) { for i := range bulk { bulk[i] = client.Document.Create(). SetName(strings.Repeat("bulk-", i+1)). - SetContent([]byte(strings.Repeat("x", i+1))). - SetThumbnail([]byte("thumb")) + SetContent(bytes.NewReader([]byte(strings.Repeat("x", i+1)))). + SetThumbnail(bytes.NewReader([]byte("thumb"))) } docs, err := client.Document.CreateBulk(bulk...).Save(ctx) require.NoError(t, err) @@ -184,8 +201,7 @@ func TestBlobBulkCreate(t *testing.T) { // Verify each document's blob can be read back with correct data. for i, doc := range docs { - got, err := doc.Content(ctx) - require.NoError(t, err) + got := blobContent(t, doc.Content, ctx) require.Equal(t, []byte(strings.Repeat("x", i+1)), got) } } @@ -196,12 +212,11 @@ func TestBlobThumbnailCreateAndRead(t *testing.T) { thumbData := []byte("fake-png-thumbnail-data") doc := client.Document.Create(). SetName("doc-with-thumb"). - SetContent([]byte("content")). - SetThumbnail(thumbData). + SetContent(bytes.NewReader([]byte("content"))). + SetThumbnail(bytes.NewReader(thumbData)). SaveX(ctx) - got, err := doc.Thumbnail(ctx) - require.NoError(t, err) + got := blobContent(t, doc.Thumbnail, ctx) require.Equal(t, thumbData, got) } @@ -212,32 +227,28 @@ func TestBlobBothFields(t *testing.T) { thumbData := []byte("thumbnail image bytes") doc := client.Document.Create(). SetName("doc-both"). - SetContent(contentData). - SetThumbnail(thumbData). + SetContent(bytes.NewReader(contentData)). + SetThumbnail(bytes.NewReader(thumbData)). SaveX(ctx) // Read content. - cGot, err := doc.Content(ctx) - require.NoError(t, err) + cGot := blobContent(t, doc.Content, ctx) require.Equal(t, contentData, cGot) // Read thumbnail. - tGot, err := doc.Thumbnail(ctx) - require.NoError(t, err) + tGot := blobContent(t, doc.Thumbnail, ctx) require.Equal(t, thumbData, tGot) // Update only thumbnail, content should remain unchanged. newThumb := []byte("updated thumbnail") doc = doc.Update(). - SetThumbnail(newThumb). + SetThumbnail(bytes.NewReader(newThumb)). SaveX(ctx) - cGot2, err := doc.Content(ctx) - require.NoError(t, err) + cGot2 := blobContent(t, doc.Content, ctx) require.Equal(t, contentData, cGot2) - tGot2, err := doc.Thumbnail(ctx) - require.NoError(t, err) + tGot2 := blobContent(t, doc.Thumbnail, ctx) require.Equal(t, newThumb, tGot2) } @@ -248,59 +259,56 @@ func TestBlobBulkCreateBothFields(t *testing.T) { for i := range bulk { bulk[i] = client.Document.Create(). SetName(strings.Repeat("bulk-both-", i+1)). - SetContent([]byte(strings.Repeat("c", i+1))). - SetThumbnail([]byte(strings.Repeat("t", i+1))) + SetContent(bytes.NewReader([]byte(strings.Repeat("c", i+1)))). + SetThumbnail(bytes.NewReader([]byte(strings.Repeat("t", i+1)))) } docs, err := client.Document.CreateBulk(bulk...).Save(ctx) require.NoError(t, err) require.Len(t, docs, 3) for i, doc := range docs { - cGot, err := doc.Content(ctx) - require.NoError(t, err) + cGot := blobContent(t, doc.Content, ctx) require.Equal(t, []byte(strings.Repeat("c", i+1)), cGot) - tGot, err := doc.Thumbnail(ctx) - require.NoError(t, err) + tGot := blobContent(t, doc.Thumbnail, ctx) require.Equal(t, []byte(strings.Repeat("t", i+1)), tGot) } } -func TestBlobOpenWriter(t *testing.T) { +func TestBlobWriter(t *testing.T) { client, ctx, _ := setupBlob(t) // Create a document with content via mutation. doc := client.Document.Create(). SetName("writer-doc"). - SetContent([]byte("initial")). - SetThumbnail([]byte("thumb")). + SetContent(strings.NewReader("initial")). + SetThumbnail(bytes.NewReader([]byte("thumb"))). SaveX(ctx) - // Overwrite via OpenContentWriter (bypasses mutation pipeline). - w, err := doc.OpenContentWriter(ctx) + // Overwrite via ContentWriter (bypasses mutation pipeline). + w, err := doc.ContentWriter(ctx) require.NoError(t, err) - _, err = w.Write([]byte("overwritten via writer")) + _, err = io.Copy(w, strings.NewReader("overwritten via writer")) require.NoError(t, err) require.NoError(t, w.Close()) // Read back should reflect the overwritten data. - got, err := doc.Content(ctx) - require.NoError(t, err) + got := blobContent(t, doc.Content, ctx) require.Equal(t, []byte("overwritten via writer"), got) } -func TestBlobOpenReader(t *testing.T) { +func TestBlobReader(t *testing.T) { client, ctx, _ := setupBlob(t) data := []byte("streaming read test data") doc := client.Document.Create(). SetName("reader-doc"). - SetContent(data). - SetThumbnail([]byte("thumb")). + SetContent(bytes.NewReader(data)). + SetThumbnail(bytes.NewReader([]byte("thumb"))). SaveX(ctx) - // Read back via OpenContentReader. - r, err := doc.OpenContentReader(ctx) + // Read back via Content (io.ReadCloser). + r, err := doc.Content(ctx) require.NoError(t, err) defer r.Close() @@ -315,12 +323,12 @@ func TestBlobWriterThenReader(t *testing.T) { // Create a document with initial content, then overwrite via Writer. doc := client.Document.Create(). SetName("write-then-read"). - SetContent([]byte("initial")). - SetThumbnail([]byte("thumb")). + SetContent(strings.NewReader("initial")). + SetThumbnail(bytes.NewReader([]byte("thumb"))). SaveX(ctx) // Write via writer. - w, err := doc.OpenContentWriter(ctx) + w, err := doc.ContentWriter(ctx) require.NoError(t, err) _, err = w.Write([]byte("hello ")) require.NoError(t, err) @@ -329,17 +337,8 @@ func TestBlobWriterThenReader(t *testing.T) { require.NoError(t, w.Close()) // Read via reader. - r, err := doc.OpenContentReader(ctx) - require.NoError(t, err) - got, err := io.ReadAll(r) - require.NoError(t, err) - r.Close() + got := blobContent(t, doc.Content, ctx) require.Equal(t, []byte("hello world"), got) - - // Also verify the convenience method sees the same data. - all, err := doc.Content(ctx) - require.NoError(t, err) - require.Equal(t, []byte("hello world"), all) } func TestBlobKey(t *testing.T) { @@ -347,8 +346,8 @@ func TestBlobKey(t *testing.T) { doc := client.Document.Create(). SetName("key-doc"). - SetContent([]byte("content")). - SetThumbnail([]byte("thumb")). + SetContent(bytes.NewReader([]byte("content"))). + SetThumbnail(bytes.NewReader([]byte("thumb"))). SaveX(ctx) // Content key follows the convention {table}/{id}/{field}. @@ -379,12 +378,11 @@ func TestBlobWriterOptions(t *testing.T) { data := []byte("content with writer options") doc := client.Document.Create(). SetName("opts-doc"). - SetContent(data). - SetThumbnail([]byte("thumb")). + SetContent(bytes.NewReader(data)). + SetThumbnail(bytes.NewReader([]byte("thumb"))). SaveX(ctx) - got, err := doc.Content(ctx) - require.NoError(t, err) + got := blobContent(t, doc.Content, ctx) require.Equal(t, data, got) } @@ -405,20 +403,18 @@ func TestBlobWriterOptionsApplied(t *testing.T) { data := []byte("content with writer options") doc := client.Document.Create(). SetName("opts-doc"). - SetContent(data). - SetThumbnail([]byte("thumb")). + SetContent(bytes.NewReader(data)). + SetThumbnail(bytes.NewReader([]byte("thumb"))). SaveX(ctx) // Verify the data was written successfully and can be read back. - got, err := doc.Content(ctx) - require.NoError(t, err) + got := blobContent(t, doc.Content, ctx) require.Equal(t, data, got) // Update also uses WriterOpts. v2 := []byte("updated with writer options") - doc = doc.Update().SetContent(v2).SaveX(ctx) - got, err = doc.Content(ctx) - require.NoError(t, err) + doc = doc.Update().SetContent(bytes.NewReader(v2)).SaveX(ctx) + got = blobContent(t, doc.Content, ctx) require.Equal(t, v2, got) } @@ -442,13 +438,12 @@ func TestBlobPrefix(t *testing.T) { data := []byte("prefixed blob content") doc := client.Document.Create(). SetName("prefixed-doc"). - SetContent(data). - SetThumbnail([]byte("thumb")). + SetContent(bytes.NewReader(data)). + SetThumbnail(bytes.NewReader([]byte("thumb"))). SaveX(ctx) // Read through the entity — uses the same opener. - got, err := doc.Content(ctx) - require.NoError(t, err) + got := blobContent(t, doc.Content, ctx) require.Equal(t, data, got) // Create a second client with the default opener — reading should return nil (not found). @@ -457,8 +452,7 @@ func TestBlobPrefix(t *testing.T) { ) t.Cleanup(func() { defaultClient.Close() }) doc2 := defaultClient.Document.Query().OnlyX(ctx) - got, err = doc2.Content(ctx) - require.NoError(t, err) + got = blobContent(t, doc2.Content, ctx) require.Nil(t, got, "expected nil when reading without prefix") } @@ -482,15 +476,14 @@ func TestBlobPrefixUpdate(t *testing.T) { data := []byte("initial") doc := client.Document.Create(). SetName("prefix-update-doc"). - SetContent(data). - SetThumbnail([]byte("thumb")). + SetContent(bytes.NewReader(data)). + SetThumbnail(bytes.NewReader([]byte("thumb"))). SaveX(ctx) updated := []byte("updated under prefix") - doc = doc.Update().SetContent(updated).SaveX(ctx) + doc = doc.Update().SetContent(bytes.NewReader(updated)).SaveX(ctx) - got, err := doc.Content(ctx) - require.NoError(t, err) + got := blobContent(t, doc.Content, ctx) require.Equal(t, updated, got) } @@ -512,18 +505,17 @@ func TestBlobPrefixBulkCreate(t *testing.T) { client, ctx, _ := setupBlob(t, ent.WithBlobOpeners(prefixedOpeners)) docs := client.Document.CreateBulk( - client.Document.Create().SetName("bulk-p1").SetContent([]byte("b1")).SetThumbnail([]byte("t1")), - client.Document.Create().SetName("bulk-p2").SetContent([]byte("b2")).SetThumbnail([]byte("t2")), + client.Document.Create().SetName("bulk-p1").SetContent(strings.NewReader("b1")).SetThumbnail(bytes.NewReader([]byte("t1"))), + client.Document.Create().SetName("bulk-p2").SetContent(strings.NewReader("b2")).SetThumbnail(bytes.NewReader([]byte("t2"))), ).SaveX(ctx) for i, doc := range docs { - got, err := doc.Content(ctx) - require.NoError(t, err) + got := blobContent(t, doc.Content, ctx) require.Equal(t, []byte(fmt.Sprintf("b%d", i+1)), got) } } -func TestBlobPrefixOpenWriterReader(t *testing.T) { +func TestBlobPrefixWriterReader(t *testing.T) { dir := blobDir(t) openers := newBlobOpeners(dir) prefixedOpeners := ent.BlobOpeners{ @@ -542,22 +534,18 @@ func TestBlobPrefixOpenWriterReader(t *testing.T) { doc := client.Document.Create(). SetName("prefix-rw-doc"). - SetContent([]byte("initial")). - SetThumbnail([]byte("thumb")). + SetContent(strings.NewReader("initial")). + SetThumbnail(bytes.NewReader([]byte("thumb"))). SaveX(ctx) - // Write via OpenContentWriter. - w, err := doc.OpenContentWriter(ctx) + // Write via ContentWriter. + w, err := doc.ContentWriter(ctx) require.NoError(t, err) - _, err = w.Write([]byte("writer-prefixed")) + _, err = io.Copy(w, strings.NewReader("writer-prefixed")) require.NoError(t, err) require.NoError(t, w.Close()) - // Read via OpenContentReader. - r, err := doc.OpenContentReader(ctx) - require.NoError(t, err) - got, err := io.ReadAll(r) - require.NoError(t, err) - require.NoError(t, r.Close()) + // Read via Content. + got := blobContent(t, doc.Content, ctx) require.Equal(t, []byte("writer-prefixed"), got) } diff --git a/entc/integration/edgeschema/ent/internal/schema.go b/entc/integration/edgeschema/ent/internal/schema.go index dfef0b861..df89c03b7 100644 --- a/entc/integration/edgeschema/ent/internal/schema.go +++ b/entc/integration/edgeschema/ent/internal/schema.go @@ -10,4 +10,4 @@ // Package internal holds a loadable version of the latest schema. package internal -const Schema = "{\"Schema\":\"entgo.io/ent/entc/integration/edgeschema/ent/schema\",\"Package\":\"entgo.io/ent/entc/integration/edgeschema/ent\",\"Schemas\":[{\"name\":\"AttachedFile\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"fi\",\"type\":\"File\",\"field\":\"f_id\",\"unique\":true,\"required\":true},{\"name\":\"proc\",\"type\":\"Process\",\"field\":\"proc_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"attach_time\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"f_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"proc_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"File\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"processes\",\"type\":\"Process\",\"ref_name\":\"files\",\"inverse\":true}],\"fields\":[{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"Friendship\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true,\"immutable\":true},{\"name\":\"friend\",\"type\":\"User\",\"field\":\"friend_id\",\"unique\":true,\"required\":true,\"immutable\":true}],\"fields\":[{\"name\":\"weight\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"friend_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"created_at\"]},{\"unique\":true,\"fields\":[\"user_id\",\"friend_id\"],\"storage_key\":\"friendships_edge\"}]},{\"name\":\"Group\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"users\",\"type\":\"User\",\"ref_name\":\"groups\",\"through\":{\"N\":\"joined_users\",\"T\":\"UserGroup\"},\"inverse\":true},{\"name\":\"tags\",\"type\":\"Tag\",\"ref_name\":\"groups\",\"through\":{\"N\":\"group_tags\",\"T\":\"GroupTag\"},\"inverse\":true}],\"fields\":[{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"Unknown\",\"default_kind\":24,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"GroupTag\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"tag\",\"type\":\"Tag\",\"field\":\"tag_id\",\"unique\":true,\"required\":true},{\"name\":\"group\",\"type\":\"Group\",\"field\":\"group_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"tag_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"group_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"Process\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"files\",\"type\":\"File\",\"through\":{\"N\":\"attached_files\",\"T\":\"AttachedFile\"},\"comment\":\"Files that were attached by this process\"}]},{\"name\":\"Relationship\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true},{\"name\":\"relative\",\"type\":\"User\",\"field\":\"relative_id\",\"unique\":true,\"required\":true},{\"name\":\"info\",\"type\":\"RelationshipInfo\",\"field\":\"info_id\",\"unique\":true}],\"fields\":[{\"name\":\"weight\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"relative_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"info_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"weight\"]},{\"unique\":true,\"edges\":[\"info\"]}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"Fields\":{\"ID\":[\"user_id\",\"relative_id\"],\"StructTag\":null}}},{\"name\":\"RelationshipInfo\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"text\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"Role\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"ref_name\":\"roles\",\"through\":{\"N\":\"roles_users\",\"T\":\"RoleUser\"},\"inverse\":true}],\"fields\":[{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"RoleUser\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"role\",\"type\":\"Role\",\"field\":\"role_id\",\"unique\":true,\"required\":true},{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"role_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}}],\"annotations\":{\"Fields\":{\"ID\":[\"user_id\",\"role_id\"],\"StructTag\":null}}},{\"name\":\"Tag\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"tweets\",\"type\":\"Tweet\",\"through\":{\"N\":\"tweet_tags\",\"T\":\"TweetTag\"}},{\"name\":\"groups\",\"type\":\"Group\",\"through\":{\"N\":\"group_tags\",\"T\":\"GroupTag\"}}],\"fields\":[{\"name\":\"value\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"Tweet\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"liked_users\",\"type\":\"User\",\"ref_name\":\"liked_tweets\",\"through\":{\"N\":\"likes\",\"T\":\"TweetLike\"},\"inverse\":true},{\"name\":\"user\",\"type\":\"User\",\"ref_name\":\"tweets\",\"through\":{\"N\":\"tweet_user\",\"T\":\"UserTweet\"},\"inverse\":true,\"comment\":\"The uniqueness is enforced on the edge schema\"},{\"name\":\"tags\",\"type\":\"Tag\",\"ref_name\":\"tweets\",\"through\":{\"N\":\"tweet_tags\",\"T\":\"TweetTag\"},\"inverse\":true}],\"fields\":[{\"name\":\"text\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"TweetLike\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"tweet\",\"type\":\"Tweet\",\"field\":\"tweet_id\",\"unique\":true,\"required\":true},{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"liked_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"tweet_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"Fields\":{\"ID\":[\"user_id\",\"tweet_id\"],\"StructTag\":null}}},{\"name\":\"TweetTag\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"tag\",\"type\":\"Tag\",\"field\":\"tag_id\",\"unique\":true,\"required\":true},{\"name\":\"tweet\",\"type\":\"Tweet\",\"field\":\"tweet_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"id\",\"type\":{\"Type\":4,\"Ident\":\"uuid.UUID\",\"PkgPath\":\"github.com/google/uuid\",\"PkgName\":\"uuid\",\"Nillable\":false,\"RType\":{\"Name\":\"UUID\",\"Ident\":\"uuid.UUID\",\"Kind\":17,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":{\"ClockSequence\":{\"In\":[],\"Out\":[{\"Name\":\"int\",\"Ident\":\"int\",\"Kind\":2,\"PkgPath\":\"\",\"Methods\":null}]},\"Domain\":{\"In\":[],\"Out\":[{\"Name\":\"Domain\",\"Ident\":\"uuid.Domain\",\"Kind\":8,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]},\"ID\":{\"In\":[],\"Out\":[{\"Name\":\"uint32\",\"Ident\":\"uint32\",\"Kind\":10,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalText\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"NodeID\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"Time\":{\"In\":[],\"Out\":[{\"Name\":\"Time\",\"Ident\":\"uuid.Time\",\"Kind\":6,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]},\"URN\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalText\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Variant\":{\"In\":[],\"Out\":[{\"Name\":\"Variant\",\"Ident\":\"uuid.Variant\",\"Kind\":8,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]},\"Version\":{\"In\":[],\"Out\":[{\"Name\":\"Version\",\"Ident\":\"uuid.Version\",\"Kind\":8,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]}}}},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"added_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"tag_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"tweet_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"User\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"groups\",\"type\":\"Group\",\"through\":{\"N\":\"joined_groups\",\"T\":\"UserGroup\"}},{\"name\":\"friends\",\"type\":\"User\",\"through\":{\"N\":\"friendships\",\"T\":\"Friendship\"}},{\"name\":\"relatives\",\"type\":\"User\",\"through\":{\"N\":\"relationship\",\"T\":\"Relationship\"}},{\"name\":\"liked_tweets\",\"type\":\"Tweet\",\"through\":{\"N\":\"likes\",\"T\":\"TweetLike\"}},{\"name\":\"tweets\",\"type\":\"Tweet\",\"through\":{\"N\":\"user_tweets\",\"T\":\"UserTweet\"}},{\"name\":\"roles\",\"type\":\"Role\",\"through\":{\"N\":\"roles_users\",\"T\":\"RoleUser\"}}],\"fields\":[{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"Unknown\",\"default_kind\":24,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]},{\"name\":\"UserGroup\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true},{\"name\":\"group\",\"type\":\"Group\",\"field\":\"group_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"joined_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"group_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"UserTweet\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true},{\"name\":\"tweet\",\"type\":\"Tweet\",\"field\":\"tweet_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"tweet_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"tweet_id\"]}]}],\"Features\":[\"entql\",\"sql/upsert\",\"privacy\",\"schema/snapshot\"]}" +const Schema = "{\"Schema\":\"entgo.io/ent/entc/integration/edgeschema/ent/schema\",\"Package\":\"entgo.io/ent/entc/integration/edgeschema/ent\",\"Schemas\":[{\"name\":\"AttachedFile\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"fi\",\"type\":\"File\",\"field\":\"f_id\",\"unique\":true,\"required\":true},{\"name\":\"proc\",\"type\":\"Process\",\"field\":\"proc_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"attach_time\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"f_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"proc_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"File\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"processes\",\"type\":\"Process\",\"ref_name\":\"files\",\"inverse\":true}],\"fields\":[{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"Friendship\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true,\"immutable\":true},{\"name\":\"friend\",\"type\":\"User\",\"field\":\"friend_id\",\"unique\":true,\"required\":true,\"immutable\":true}],\"fields\":[{\"name\":\"weight\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"friend_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"created_at\"]},{\"unique\":true,\"fields\":[\"user_id\",\"friend_id\"],\"storage_key\":\"friendships_edge\"}]},{\"name\":\"Group\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"users\",\"type\":\"User\",\"ref_name\":\"groups\",\"through\":{\"N\":\"joined_users\",\"T\":\"UserGroup\"},\"inverse\":true},{\"name\":\"tags\",\"type\":\"Tag\",\"ref_name\":\"groups\",\"through\":{\"N\":\"group_tags\",\"T\":\"GroupTag\"},\"inverse\":true}],\"fields\":[{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"Unknown\",\"default_kind\":24,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"GroupTag\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"tag\",\"type\":\"Tag\",\"field\":\"tag_id\",\"unique\":true,\"required\":true},{\"name\":\"group\",\"type\":\"Group\",\"field\":\"group_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"tag_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"group_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"Process\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"files\",\"type\":\"File\",\"through\":{\"N\":\"attached_files\",\"T\":\"AttachedFile\"},\"comment\":\"Files that were attached by this process\"}]},{\"name\":\"Relationship\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true},{\"name\":\"relative\",\"type\":\"User\",\"field\":\"relative_id\",\"unique\":true,\"required\":true},{\"name\":\"info\",\"type\":\"RelationshipInfo\",\"field\":\"info_id\",\"unique\":true}],\"fields\":[{\"name\":\"weight\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"relative_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"info_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"weight\"]},{\"unique\":true,\"edges\":[\"info\"]}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"Fields\":{\"ID\":[\"user_id\",\"relative_id\"],\"StructTag\":null}}},{\"name\":\"RelationshipInfo\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"text\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"Role\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"ref_name\":\"roles\",\"through\":{\"N\":\"roles_users\",\"T\":\"RoleUser\"},\"inverse\":true}],\"fields\":[{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"RoleUser\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"role\",\"type\":\"Role\",\"field\":\"role_id\",\"unique\":true,\"required\":true},{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"role_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}}],\"annotations\":{\"Fields\":{\"ID\":[\"user_id\",\"role_id\"],\"StructTag\":null}}},{\"name\":\"Tag\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"tweets\",\"type\":\"Tweet\",\"through\":{\"N\":\"tweet_tags\",\"T\":\"TweetTag\"}},{\"name\":\"groups\",\"type\":\"Group\",\"through\":{\"N\":\"group_tags\",\"T\":\"GroupTag\"}}],\"fields\":[{\"name\":\"value\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"Tweet\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"liked_users\",\"type\":\"User\",\"ref_name\":\"liked_tweets\",\"through\":{\"N\":\"likes\",\"T\":\"TweetLike\"},\"inverse\":true},{\"name\":\"user\",\"type\":\"User\",\"ref_name\":\"tweets\",\"through\":{\"N\":\"tweet_user\",\"T\":\"UserTweet\"},\"inverse\":true,\"comment\":\"The uniqueness is enforced on the edge schema\"},{\"name\":\"tags\",\"type\":\"Tag\",\"ref_name\":\"tweets\",\"through\":{\"N\":\"tweet_tags\",\"T\":\"TweetTag\"},\"inverse\":true}],\"fields\":[{\"name\":\"text\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":2147483647,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"TweetLike\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"tweet\",\"type\":\"Tweet\",\"field\":\"tweet_id\",\"unique\":true,\"required\":true},{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"liked_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"tweet_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"Fields\":{\"ID\":[\"user_id\",\"tweet_id\"],\"StructTag\":null}}},{\"name\":\"TweetTag\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"tag\",\"type\":\"Tag\",\"field\":\"tag_id\",\"unique\":true,\"required\":true},{\"name\":\"tweet\",\"type\":\"Tweet\",\"field\":\"tweet_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"id\",\"type\":{\"Type\":4,\"Ident\":\"uuid.UUID\",\"PkgPath\":\"github.com/google/uuid\",\"PkgName\":\"uuid\",\"Nillable\":false,\"RType\":{\"Name\":\"UUID\",\"Ident\":\"uuid.UUID\",\"Kind\":17,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":{\"ClockSequence\":{\"In\":[],\"Out\":[{\"Name\":\"int\",\"Ident\":\"int\",\"Kind\":2,\"PkgPath\":\"\",\"Methods\":null}]},\"Domain\":{\"In\":[],\"Out\":[{\"Name\":\"Domain\",\"Ident\":\"uuid.Domain\",\"Kind\":8,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]},\"ID\":{\"In\":[],\"Out\":[{\"Name\":\"uint32\",\"Ident\":\"uint32\",\"Kind\":10,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalText\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"NodeID\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"Time\":{\"In\":[],\"Out\":[{\"Name\":\"Time\",\"Ident\":\"uuid.Time\",\"Kind\":6,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]},\"URN\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalText\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Variant\":{\"In\":[],\"Out\":[{\"Name\":\"Variant\",\"Ident\":\"uuid.Variant\",\"Kind\":8,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]},\"Version\":{\"In\":[],\"Out\":[{\"Name\":\"Version\",\"Ident\":\"uuid.Version\",\"Kind\":8,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]}}}},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"added_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"tag_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"tweet_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"User\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"groups\",\"type\":\"Group\",\"through\":{\"N\":\"joined_groups\",\"T\":\"UserGroup\"}},{\"name\":\"friends\",\"type\":\"User\",\"through\":{\"N\":\"friendships\",\"T\":\"Friendship\"}},{\"name\":\"relatives\",\"type\":\"User\",\"through\":{\"N\":\"relationship\",\"T\":\"Relationship\"}},{\"name\":\"liked_tweets\",\"type\":\"Tweet\",\"through\":{\"N\":\"likes\",\"T\":\"TweetLike\"}},{\"name\":\"tweets\",\"type\":\"Tweet\",\"through\":{\"N\":\"user_tweets\",\"T\":\"UserTweet\"}},{\"name\":\"roles\",\"type\":\"Role\",\"through\":{\"N\":\"roles_users\",\"T\":\"RoleUser\"}}],\"fields\":[{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"Unknown\",\"default_kind\":24,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]},{\"name\":\"UserGroup\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true},{\"name\":\"group\",\"type\":\"Group\",\"field\":\"group_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"joined_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"group_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}}]},{\"name\":\"UserTweet\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true},{\"name\":\"tweet\",\"type\":\"Tweet\",\"field\":\"tweet_id\",\"unique\":true,\"required\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"user_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"tweet_id\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"unique\":true,\"fields\":[\"tweet_id\"]}]}],\"Features\":[\"entql\",\"sql/upsert\",\"privacy\",\"schema/snapshot\"]}" diff --git a/entc/integration/ent/document.go b/entc/integration/ent/document.go index 5e2abe5e1..5e2f13525 100644 --- a/entc/integration/ent/document.go +++ b/entc/integration/ent/document.go @@ -80,23 +80,10 @@ func (_m *Document) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } -// Content reads the blob data for the "content" field from blob storage. -// It returns nil, nil if the blob does not exist. -func (_m *Document) Content(ctx context.Context) ([]byte, error) { - switch r, err := _m.OpenContentReader(ctx); { - case errors.Is(err, fs.ErrNotExist): - return nil, nil - case err != nil: - return nil, err - default: - defer r.Close() - return io.ReadAll(r) - } -} - -// OpenContentReader opens a reader for the "content" field. +// Content opens a reader for the "content" field from blob storage. // The caller must close the returned reader when done. -func (_m *Document) OpenContentReader(ctx context.Context) (io.ReadCloser, error) { +// It returns nil, nil if the blob does not exist. +func (_m *Document) Content(ctx context.Context) (io.ReadCloser, error) { key, err := _m.ContentKey(ctx) if err != nil { return nil, fmt.Errorf("ent: blob key for content: %w", err) @@ -105,17 +92,20 @@ func (_m *Document) OpenContentReader(ctx context.Context) (io.ReadCloser, error if err != nil { return nil, fmt.Errorf("ent: opening blob bucket for content: %w", err) } - r, err := b.NewReader(ctx, key) - if err != nil { + switch r, err := b.NewReader(ctx, key); { + case errors.Is(err, fs.ErrNotExist): + return nil, b.Close() + case err != nil: return nil, errors.Join(fmt.Errorf("ent: creating reader for content: %w", err), b.Close()) + default: + return r, nil } - return r, nil } -// OpenContentWriter opens a writer for the "content" field. +// ContentWriter opens a writer for the "content" field in blob storage. // The caller must close the returned writer when done. // Writing via this method does not go through the mutation pipeline. -func (_m *Document) OpenContentWriter(ctx context.Context) (io.WriteCloser, error) { +func (_m *Document) ContentWriter(ctx context.Context) (io.WriteCloser, error) { key, err := _m.ContentKey(ctx) if err != nil { return nil, fmt.Errorf("ent: blob key for content: %w", err) @@ -136,23 +126,10 @@ func (_m *Document) ContentKey(context.Context) (string, error) { return fmt.Sprintf("%s/%v/%s", "documents", _m.ID, "content"), nil } -// Thumbnail reads the blob data for the "thumbnail" field from blob storage. -// It returns nil, nil if the blob does not exist. -func (_m *Document) Thumbnail(ctx context.Context) ([]byte, error) { - switch r, err := _m.OpenThumbnailReader(ctx); { - case errors.Is(err, fs.ErrNotExist): - return nil, nil - case err != nil: - return nil, err - default: - defer r.Close() - return io.ReadAll(r) - } -} - -// OpenThumbnailReader opens a reader for the "thumbnail" field. +// Thumbnail opens a reader for the "thumbnail" field from blob storage. // The caller must close the returned reader when done. -func (_m *Document) OpenThumbnailReader(ctx context.Context) (io.ReadCloser, error) { +// It returns nil, nil if the blob does not exist. +func (_m *Document) Thumbnail(ctx context.Context) (io.ReadCloser, error) { key, err := _m.ThumbnailKey(ctx) if err != nil { return nil, fmt.Errorf("ent: blob key for thumbnail: %w", err) @@ -161,17 +138,20 @@ func (_m *Document) OpenThumbnailReader(ctx context.Context) (io.ReadCloser, err if err != nil { return nil, fmt.Errorf("ent: opening blob bucket for thumbnail: %w", err) } - r, err := b.NewReader(ctx, key) - if err != nil { + switch r, err := b.NewReader(ctx, key); { + case errors.Is(err, fs.ErrNotExist): + return nil, b.Close() + case err != nil: return nil, errors.Join(fmt.Errorf("ent: creating reader for thumbnail: %w", err), b.Close()) + default: + return r, nil } - return r, nil } -// OpenThumbnailWriter opens a writer for the "thumbnail" field. +// ThumbnailWriter opens a writer for the "thumbnail" field in blob storage. // The caller must close the returned writer when done. // Writing via this method does not go through the mutation pipeline. -func (_m *Document) OpenThumbnailWriter(ctx context.Context) (io.WriteCloser, error) { +func (_m *Document) ThumbnailWriter(ctx context.Context) (io.WriteCloser, error) { key, err := _m.ThumbnailKey(ctx) if err != nil { return nil, fmt.Errorf("ent: blob key for thumbnail: %w", err) diff --git a/entc/integration/ent/document/mutation.go b/entc/integration/ent/document/mutation.go index 056ded539..bd3313063 100644 --- a/entc/integration/ent/document/mutation.go +++ b/entc/integration/ent/document/mutation.go @@ -9,6 +9,7 @@ package document import ( "context" "fmt" + "io" "entgo.io/ent" "entgo.io/ent/dialect/sql" @@ -20,8 +21,8 @@ type Mutation struct { op ent.Op typ string name *string - content *[]byte - thumbnail *[]byte + content io.Reader + thumbnail io.Reader clearedFields map[string]struct{} predicates []predicate.Document } @@ -60,17 +61,17 @@ func (m *Mutation) ResetName() { } // SetContent sets the "content" field. -func (m *Mutation) SetContent(b []byte) { - m.content = &b +func (m *Mutation) SetContent(r io.Reader) { + m.content = r } // Content returns the value of the "content" field in the mutation. -func (m *Mutation) Content() (r []byte, exists bool) { +func (m *Mutation) Content() (r io.Reader, exists bool) { v := m.content if v == nil { return } - return *v, true + return v, true } // ResetContent resets all changes to the "content" field. @@ -79,17 +80,17 @@ func (m *Mutation) ResetContent() { } // SetThumbnail sets the "thumbnail" field. -func (m *Mutation) SetThumbnail(b []byte) { - m.thumbnail = &b +func (m *Mutation) SetThumbnail(r io.Reader) { + m.thumbnail = r } // Thumbnail returns the value of the "thumbnail" field in the mutation. -func (m *Mutation) Thumbnail() (r []byte, exists bool) { +func (m *Mutation) Thumbnail() (r io.Reader, exists bool) { v := m.thumbnail if v == nil { return } - return *v, true + return v, true } // ResetThumbnail resets all changes to the "thumbnail" field. diff --git a/entc/integration/ent/document_create.go b/entc/integration/ent/document_create.go index de79986bb..ae21aeea0 100644 --- a/entc/integration/ent/document_create.go +++ b/entc/integration/ent/document_create.go @@ -10,6 +10,7 @@ import ( "context" "errors" "fmt" + "io" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -32,13 +33,13 @@ func (_c *DocumentCreate) SetName(v string) *DocumentCreate { } // SetContent sets the "content" field. -func (_c *DocumentCreate) SetContent(v []byte) *DocumentCreate { +func (_c *DocumentCreate) SetContent(v io.Reader) *DocumentCreate { _c.mutation.SetContent(v) return _c } // SetThumbnail sets the "thumbnail" field. -func (_c *DocumentCreate) SetThumbnail(v []byte) *DocumentCreate { +func (_c *DocumentCreate) SetThumbnail(v io.Reader) *DocumentCreate { _c.mutation.SetThumbnail(v) return _c } @@ -104,7 +105,7 @@ func (_c *DocumentCreate) sqlSave(ctx context.Context) (*Document, error) { _node.ID = int(id) _c.mutation.id = &_node.ID _c.mutation.done = true - if data, ok := _c.mutation.Content(); ok { + if r, ok := _c.mutation.Content(); ok { b, err := _c.mutation.blobOpeners.Document(ctx, document.FieldContent) if err != nil { return nil, fmt.Errorf("ent: opening blob bucket for content: %w", err) @@ -117,14 +118,14 @@ func (_c *DocumentCreate) sqlSave(ctx context.Context) (*Document, error) { if err != nil { return nil, errors.Join(fmt.Errorf("ent: creating writer for content: %w", err), b.Close()) } - if _, err := w.Write(data); err != nil { + if _, err := io.Copy(w, r); err != nil { return nil, errors.Join(fmt.Errorf("ent: writing blob for content: %w", err), w.Close(), b.Close()) } if err := errors.Join(w.Close(), b.Close()); err != nil { return nil, fmt.Errorf("ent: closing blob for content: %w", err) } } - if data, ok := _c.mutation.Thumbnail(); ok { + if r, ok := _c.mutation.Thumbnail(); ok { b, err := _c.mutation.blobOpeners.Document(ctx, document.FieldThumbnail) if err != nil { return nil, fmt.Errorf("ent: opening blob bucket for thumbnail: %w", err) @@ -137,7 +138,7 @@ func (_c *DocumentCreate) sqlSave(ctx context.Context) (*Document, error) { if err != nil { return nil, errors.Join(fmt.Errorf("ent: creating writer for thumbnail: %w", err), b.Close()) } - if _, err := w.Write(data); err != nil { + if _, err := io.Copy(w, r); err != nil { return nil, errors.Join(fmt.Errorf("ent: writing blob for thumbnail: %w", err), w.Close(), b.Close()) } if err := errors.Join(w.Close(), b.Close()); err != nil { @@ -222,7 +223,7 @@ func (u *DocumentUpsert) UpdateName() *DocumentUpsert { } // SetContent sets the "content" field. -func (u *DocumentUpsert) SetContent(v []byte) *DocumentUpsert { +func (u *DocumentUpsert) SetContent(v io.Reader) *DocumentUpsert { u.Set(document.FieldContent, v) return u } @@ -234,7 +235,7 @@ func (u *DocumentUpsert) UpdateContent() *DocumentUpsert { } // SetThumbnail sets the "thumbnail" field. -func (u *DocumentUpsert) SetThumbnail(v []byte) *DocumentUpsert { +func (u *DocumentUpsert) SetThumbnail(v io.Reader) *DocumentUpsert { u.Set(document.FieldThumbnail, v) return u } @@ -300,7 +301,7 @@ func (u *DocumentUpsertOne) UpdateName() *DocumentUpsertOne { } // SetContent sets the "content" field. -func (u *DocumentUpsertOne) SetContent(v []byte) *DocumentUpsertOne { +func (u *DocumentUpsertOne) SetContent(v io.Reader) *DocumentUpsertOne { return u.Update(func(s *DocumentUpsert) { s.SetContent(v) }) @@ -314,7 +315,7 @@ func (u *DocumentUpsertOne) UpdateContent() *DocumentUpsertOne { } // SetThumbnail sets the "thumbnail" field. -func (u *DocumentUpsertOne) SetThumbnail(v []byte) *DocumentUpsertOne { +func (u *DocumentUpsertOne) SetThumbnail(v io.Reader) *DocumentUpsertOne { return u.Update(func(s *DocumentUpsert) { s.SetThumbnail(v) }) @@ -423,7 +424,7 @@ func (_c *DocumentCreateBulk) Save(ctx context.Context) ([]*Document, error) { nodes[i].ID = int(id) } mutation.done = true - if data, ok := mutation.Content(); ok { + if r, ok := mutation.Content(); ok { if _blobContent == nil { if _blobContent, err = mutation.blobOpeners.Document(ctx, document.FieldContent); err != nil { return nil, fmt.Errorf("ent: opening blob bucket for content: %w", err) @@ -437,14 +438,14 @@ func (_c *DocumentCreateBulk) Save(ctx context.Context) ([]*Document, error) { if err != nil { return nil, fmt.Errorf("ent: creating writer for content: %w", err) } - if _, err := w.Write(data); err != nil { + if _, err := io.Copy(w, r); err != nil { return nil, errors.Join(fmt.Errorf("ent: writing blob for content: %w", err), w.Close()) } if err := w.Close(); err != nil { return nil, fmt.Errorf("ent: closing writer for content: %w", err) } } - if data, ok := mutation.Thumbnail(); ok { + if r, ok := mutation.Thumbnail(); ok { if _blobThumbnail == nil { if _blobThumbnail, err = mutation.blobOpeners.Document(ctx, document.FieldThumbnail); err != nil { return nil, fmt.Errorf("ent: opening blob bucket for thumbnail: %w", err) @@ -458,7 +459,7 @@ func (_c *DocumentCreateBulk) Save(ctx context.Context) ([]*Document, error) { if err != nil { return nil, fmt.Errorf("ent: creating writer for thumbnail: %w", err) } - if _, err := w.Write(data); err != nil { + if _, err := io.Copy(w, r); err != nil { return nil, errors.Join(fmt.Errorf("ent: writing blob for thumbnail: %w", err), w.Close()) } if err := w.Close(); err != nil { @@ -599,7 +600,7 @@ func (u *DocumentUpsertBulk) UpdateName() *DocumentUpsertBulk { } // SetContent sets the "content" field. -func (u *DocumentUpsertBulk) SetContent(v []byte) *DocumentUpsertBulk { +func (u *DocumentUpsertBulk) SetContent(v io.Reader) *DocumentUpsertBulk { return u.Update(func(s *DocumentUpsert) { s.SetContent(v) }) @@ -613,7 +614,7 @@ func (u *DocumentUpsertBulk) UpdateContent() *DocumentUpsertBulk { } // SetThumbnail sets the "thumbnail" field. -func (u *DocumentUpsertBulk) SetThumbnail(v []byte) *DocumentUpsertBulk { +func (u *DocumentUpsertBulk) SetThumbnail(v io.Reader) *DocumentUpsertBulk { return u.Update(func(s *DocumentUpsert) { s.SetThumbnail(v) }) diff --git a/entc/integration/ent/document_update.go b/entc/integration/ent/document_update.go index 89a27e0ea..18122c31d 100644 --- a/entc/integration/ent/document_update.go +++ b/entc/integration/ent/document_update.go @@ -10,6 +10,7 @@ import ( "context" "errors" "fmt" + "io" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" @@ -47,13 +48,13 @@ func (_u *DocumentUpdate) SetNillableName(v *string) *DocumentUpdate { } // SetContent sets the "content" field. -func (_u *DocumentUpdate) SetContent(v []byte) *DocumentUpdate { +func (_u *DocumentUpdate) SetContent(v io.Reader) *DocumentUpdate { _u.mutation.SetContent(v) return _u } // SetThumbnail sets the "thumbnail" field. -func (_u *DocumentUpdate) SetThumbnail(v []byte) *DocumentUpdate { +func (_u *DocumentUpdate) SetThumbnail(v io.Reader) *DocumentUpdate { _u.mutation.SetThumbnail(v) return _u } @@ -145,13 +146,13 @@ func (_u *DocumentUpdateOne) SetNillableName(v *string) *DocumentUpdateOne { } // SetContent sets the "content" field. -func (_u *DocumentUpdateOne) SetContent(v []byte) *DocumentUpdateOne { +func (_u *DocumentUpdateOne) SetContent(v io.Reader) *DocumentUpdateOne { _u.mutation.SetContent(v) return _u } // SetThumbnail sets the "thumbnail" field. -func (_u *DocumentUpdateOne) SetThumbnail(v []byte) *DocumentUpdateOne { +func (_u *DocumentUpdateOne) SetThumbnail(v io.Reader) *DocumentUpdateOne { _u.mutation.SetThumbnail(v) return _u } @@ -249,7 +250,7 @@ func (_u *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err return nil, err } _u.mutation.done = true - if data, ok := _u.mutation.Content(); ok { + if r, ok := _u.mutation.Content(); ok { b, err := _u.mutation.blobOpeners.Document(ctx, document.FieldContent) if err != nil { return nil, fmt.Errorf("ent: opening blob bucket for content: %w", err) @@ -262,14 +263,14 @@ func (_u *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err if err != nil { return nil, errors.Join(fmt.Errorf("ent: creating writer for content: %w", err), b.Close()) } - if _, err := w.Write(data); err != nil { + if _, err := io.Copy(w, r); err != nil { return nil, errors.Join(fmt.Errorf("ent: writing blob for content: %w", err), w.Close(), b.Close()) } if err := errors.Join(w.Close(), b.Close()); err != nil { return nil, fmt.Errorf("ent: closing blob for content: %w", err) } } - if data, ok := _u.mutation.Thumbnail(); ok { + if r, ok := _u.mutation.Thumbnail(); ok { b, err := _u.mutation.blobOpeners.Document(ctx, document.FieldThumbnail) if err != nil { return nil, fmt.Errorf("ent: opening blob bucket for thumbnail: %w", err) @@ -282,7 +283,7 @@ func (_u *DocumentUpdateOne) sqlSave(ctx context.Context) (_node *Document, err if err != nil { return nil, errors.Join(fmt.Errorf("ent: creating writer for thumbnail: %w", err), b.Close()) } - if _, err := w.Write(data); err != nil { + if _, err := io.Copy(w, r); err != nil { return nil, errors.Join(fmt.Errorf("ent: writing blob for thumbnail: %w", err), w.Close(), b.Close()) } if err := errors.Join(w.Close(), b.Close()); err != nil { diff --git a/entc/integration/ent/schema/document.go b/entc/integration/ent/schema/document.go index b6d08f9aa..8cf5026e7 100644 --- a/entc/integration/ent/schema/document.go +++ b/entc/integration/ent/schema/document.go @@ -18,7 +18,7 @@ type Document struct { func (Document) Fields() []ent.Field { return []ent.Field{ field.String("name"), - field.Bytes("content").Blob(), - field.Bytes("thumbnail").Blob(), + field.Blob("content"), + field.Blob("thumbnail"), } } diff --git a/entc/integration/go.mod b/entc/integration/go.mod index 1cf8ccb24..60dfb0988 100644 --- a/entc/integration/go.mod +++ b/entc/integration/go.mod @@ -1,6 +1,6 @@ module entgo.io/ent/entc/integration -go 1.24.0 +go 1.25.0 replace entgo.io/ent => ../../ @@ -14,7 +14,7 @@ require ( github.com/mattn/go-sqlite3 v1.14.28 github.com/stretchr/testify v1.11.1 gocloud.dev v0.45.0 - golang.org/x/sync v0.18.0 + golang.org/x/sync v0.20.0 ) require ( @@ -45,10 +45,11 @@ require ( go.opentelemetry.io/otel/sdk v1.40.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect go.opentelemetry.io/otel/trace v1.40.0 // indirect - golang.org/x/mod v0.29.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect + golang.org/x/tools v0.44.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/api v0.256.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect diff --git a/entc/integration/go.sum b/entc/integration/go.sum index c8a421274..205f6b25f 100644 --- a/entc/integration/go.sum +++ b/entc/integration/go.sum @@ -76,6 +76,12 @@ github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQ github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo= +github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= +github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= +github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= +github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= +github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/cncf/xds/go v0.0.0-20251110193048-8bfbf64dc13e h1:gt7U1Igw0xbJdyaCM5H2CnlAlPSkzrhsebQB6WQWjLA= github.com/cncf/xds/go v0.0.0-20251110193048-8bfbf64dc13e/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -86,6 +92,8 @@ github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3re github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= @@ -130,6 +138,12 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -141,6 +155,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= +github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= +github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 h1:jrYnow5+hy3WRDCBypUFvVKNSPPCdqgSXIE9eJDD8LM= +github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= +github.com/olekukonko/tablewriter v1.1.3 h1:VSHhghXxrP0JHl+0NnKid7WoEmd9/urKRJLysb70nnA= +github.com/olekukonko/tablewriter v1.1.3/go.mod h1:9VU0knjhmMkXjnMKrZ3+L2JhhtsQ/L38BbL3CRNE8tM= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -149,6 +171,10 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -183,20 +209,33 @@ gocloud.dev v0.45.0 h1:WknIK8IbRdmynDvara3Q7G6wQhmEiOGwpgJufbM39sY= gocloud.dev v0.45.0/go.mod h1:0kXKmkCLG6d31N7NyLZWzt7jDSQura9zD/mWgiB6THI= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= diff --git a/entc/integration/gremlin/ent/client.go b/entc/integration/gremlin/ent/client.go index 2b2791112..72dd36787 100644 --- a/entc/integration/gremlin/ent/client.go +++ b/entc/integration/gremlin/ent/client.go @@ -10,6 +10,7 @@ import ( "context" "errors" "fmt" + "io" "log" "net/url" "reflect" @@ -23,6 +24,7 @@ import ( "entgo.io/ent/entc/integration/gremlin/ent/builder" "entgo.io/ent/entc/integration/gremlin/ent/card" "entgo.io/ent/entc/integration/gremlin/ent/comment" + "entgo.io/ent/entc/integration/gremlin/ent/document" "entgo.io/ent/entc/integration/gremlin/ent/exvaluescan" "entgo.io/ent/entc/integration/gremlin/ent/fieldtype" "entgo.io/ent/entc/integration/gremlin/ent/file" @@ -51,6 +53,8 @@ type Client struct { Card *CardClient // Comment is the client for interacting with the Comment builders. Comment *CommentClient + // Document is the client for interacting with the Document builders. + Document *DocumentClient // ExValueScan is the client for interacting with the ExValueScan builders. ExValueScan *ExValueScanClient // FieldType is the client for interacting with the FieldType builders. @@ -96,6 +100,7 @@ func (c *Client) init() { c.Builder = NewBuilderClient(c.config) c.Card = NewCardClient(c.config) c.Comment = NewCommentClient(c.config) + c.Document = NewDocumentClient(c.config) c.ExValueScan = NewExValueScanClient(c.config) c.FieldType = NewFieldTypeClient(c.config) c.File = NewFileClient(c.config) @@ -126,6 +131,8 @@ type ( hooks *hooks // interceptors to execute on queries. inters *inters + // blobOpeners configures how blob buckets are opened for each entity type. + blobOpeners BlobOpeners } // Option function to configure the client. Option func(*config) @@ -169,6 +176,31 @@ func Driver(driver dialect.Driver) Option { } } +// Blob defines the interface for blob storage operations. +// Implementations should return [io/fs.ErrNotExist] (or an error wrapping it) +// from NewReader when the requested key does not exist. +type Blob interface { + // NewReader opens a reader for the given key. + NewReader(ctx context.Context, key string) (io.ReadCloser, error) + // NewWriter opens a writer for the given key. + NewWriter(ctx context.Context, key string) (io.WriteCloser, error) + // Close releases any resources held by the bucket. + Close() error +} + +// BlobOpeners configures how blob buckets are opened for each entity type. +// Each field is a function that opens a blob bucket for the given field name. +type BlobOpeners struct { + Document func(context.Context, string) (Blob, error) +} + +// WithBlobOpeners configures the blob bucket openers. +func WithBlobOpeners(openers BlobOpeners) Option { + return func(c *config) { + c.blobOpeners = openers + } +} + // Open opens a database/sql.DB specified by the driver name and // the data source name, and returns a new client attached to it. // Optional parameters can be added for configuring the client. @@ -216,6 +248,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { Builder: NewBuilderClient(cfg), Card: NewCardClient(cfg), Comment: NewCommentClient(cfg), + Document: NewDocumentClient(cfg), ExValueScan: NewExValueScanClient(cfg), FieldType: NewFieldTypeClient(cfg), File: NewFileClient(cfg), @@ -260,9 +293,9 @@ func (c *Client) Close() error { // In order to add hooks to a specific client, call: `client.Node.Use(...)`. func (c *Client) Use(hooks ...Hook) { for _, n := range []interface{ Use(...Hook) }{ - c.Api, c.Builder, c.Card, c.Comment, c.ExValueScan, c.FieldType, c.File, - c.FileType, c.Goods, c.Group, c.GroupInfo, c.Item, c.License, c.Node, c.PC, - c.Pet, c.Spec, c.Task, c.User, + c.Api, c.Builder, c.Card, c.Comment, c.Document, c.ExValueScan, c.FieldType, + c.File, c.FileType, c.Goods, c.Group, c.GroupInfo, c.Item, c.License, c.Node, + c.PC, c.Pet, c.Spec, c.Task, c.User, } { n.Use(hooks...) } @@ -272,9 +305,9 @@ func (c *Client) Use(hooks ...Hook) { // In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. func (c *Client) Intercept(interceptors ...Interceptor) { for _, n := range []interface{ Intercept(...Interceptor) }{ - c.Api, c.Builder, c.Card, c.Comment, c.ExValueScan, c.FieldType, c.File, - c.FileType, c.Goods, c.Group, c.GroupInfo, c.Item, c.License, c.Node, c.PC, - c.Pet, c.Spec, c.Task, c.User, + c.Api, c.Builder, c.Card, c.Comment, c.Document, c.ExValueScan, c.FieldType, + c.File, c.FileType, c.Goods, c.Group, c.GroupInfo, c.Item, c.License, c.Node, + c.PC, c.Pet, c.Spec, c.Task, c.User, } { n.Intercept(interceptors...) } @@ -301,6 +334,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { return c.Card.mutate(ctx, m) case *CommentMutation: return c.Comment.mutate(ctx, m) + case *DocumentMutation: + return c.Document.mutate(ctx, m) case *ExValueScanMutation: return c.ExValueScan.mutate(ctx, m) case *FieldTypeMutation: @@ -890,6 +925,139 @@ func (c *CommentClient) mutate(ctx context.Context, m *CommentMutation) (Value, } } +// DocumentClient is a client for the Document schema. +type DocumentClient struct { + config +} + +// NewDocumentClient returns a client for the Document from the given config. +func NewDocumentClient(c config) *DocumentClient { + return &DocumentClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `document.Hooks(f(g(h())))`. +func (c *DocumentClient) Use(hooks ...Hook) { + c.hooks.Document = append(c.hooks.Document, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `document.Intercept(f(g(h())))`. +func (c *DocumentClient) Intercept(interceptors ...Interceptor) { + c.inters.Document = append(c.inters.Document, interceptors...) +} + +// Create returns a builder for creating a Document entity. +func (c *DocumentClient) Create() *DocumentCreate { + mutation := newDocumentMutation(c.config, OpCreate) + return &DocumentCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of Document entities. +func (c *DocumentClient) CreateBulk(builders ...*DocumentCreate) *DocumentCreateBulk { + return &DocumentCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *DocumentClient) MapCreateBulk(slice any, setFunc func(*DocumentCreate, int)) *DocumentCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &DocumentCreateBulk{err: fmt.Errorf("calling to DocumentClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*DocumentCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &DocumentCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for Document. +func (c *DocumentClient) Update() *DocumentUpdate { + mutation := newDocumentMutation(c.config, OpUpdate) + return &DocumentUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *DocumentClient) UpdateOne(_m *Document) *DocumentUpdateOne { + mutation := newDocumentMutation(c.config, OpUpdateOne, withDocument(_m)) + return &DocumentUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *DocumentClient) UpdateOneID(id string) *DocumentUpdateOne { + mutation := newDocumentMutation(c.config, OpUpdateOne, withDocumentID(id)) + return &DocumentUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for Document. +func (c *DocumentClient) Delete() *DocumentDelete { + mutation := newDocumentMutation(c.config, OpDelete) + return &DocumentDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *DocumentClient) DeleteOne(_m *Document) *DocumentDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *DocumentClient) DeleteOneID(id string) *DocumentDeleteOne { + builder := c.Delete().Where(document.ID(id)) + builder.mutation.id = &id + builder.mutation.SetOp(OpDeleteOne) + return &DocumentDeleteOne{builder} +} + +// Query returns a query builder for Document. +func (c *DocumentClient) Query() *DocumentQuery { + return &DocumentQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeDocument}, + inters: c.Interceptors(), + } +} + +// Get returns a Document entity by its id. +func (c *DocumentClient) Get(ctx context.Context, id string) (*Document, error) { + return c.Query().Where(document.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *DocumentClient) GetX(ctx context.Context, id string) *Document { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// Hooks returns the client hooks. +func (c *DocumentClient) Hooks() []Hook { + return c.hooks.Document +} + +// Interceptors returns the client interceptors. +func (c *DocumentClient) Interceptors() []Interceptor { + return c.inters.Document +} + +func (c *DocumentClient) mutate(ctx context.Context, m *DocumentMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&DocumentCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&DocumentUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&DocumentUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&DocumentDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown Document mutation op: %q", m.Op()) + } +} + // ExValueScanClient is a client for the ExValueScan schema. type ExValueScanClient struct { config @@ -3163,12 +3331,13 @@ func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error) // hooks and interceptors per client, for fast access. type ( hooks struct { - Api, Builder, Card, Comment, ExValueScan, FieldType, File, FileType, Goods, - Group, GroupInfo, Item, License, Node, PC, Pet, Spec, Task, User []ent.Hook + Api, Builder, Card, Comment, Document, ExValueScan, FieldType, File, FileType, + Goods, Group, GroupInfo, Item, License, Node, PC, Pet, Spec, Task, + User []ent.Hook } inters struct { - Api, Builder, Card, Comment, ExValueScan, FieldType, File, FileType, Goods, - Group, GroupInfo, Item, License, Node, PC, Pet, Spec, Task, + Api, Builder, Card, Comment, Document, ExValueScan, FieldType, File, FileType, + Goods, Group, GroupInfo, Item, License, Node, PC, Pet, Spec, Task, User []ent.Interceptor } ) diff --git a/entc/integration/gremlin/ent/document.go b/entc/integration/gremlin/ent/document.go new file mode 100644 index 000000000..3b90b9b29 --- /dev/null +++ b/entc/integration/gremlin/ent/document.go @@ -0,0 +1,202 @@ +// 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 by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io" + "io/fs" + "strings" + + "entgo.io/ent/dialect/gremlin" + "entgo.io/ent/entc/integration/gremlin/ent/document" +) + +// Document is the model entity for the Document schema. +type Document struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // Name holds the value of the "name" field. + Name string `json:"name,omitempty"` +} + +// FromResponse scans the gremlin response data into Document. +func (_m *Document) FromResponse(res *gremlin.Response) error { + vmap, err := res.ReadValueMap() + if err != nil { + return err + } + var scan_m struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Content io.Reader `json:"content,omitempty"` + Thumbnail io.Reader `json:"thumbnail,omitempty"` + } + if err := vmap.Decode(&scan_m); err != nil { + return err + } + _m.ID = scan_m.ID + _m.Name = scan_m.Name + _m.Content = scan_m.Content + _m.Thumbnail = scan_m.Thumbnail + return nil +} + +// Content opens a reader for the "content" field from blob storage. +// The caller must close the returned reader when done. +// It returns nil, nil if the blob does not exist. +func (_m *Document) Content(ctx context.Context) (io.ReadCloser, error) { + key, err := _m.ContentKey(ctx) + if err != nil { + return nil, fmt.Errorf("ent: blob key for content: %w", err) + } + b, err := _m.blobOpeners.Document(ctx, document.FieldContent) + if err != nil { + return nil, fmt.Errorf("ent: opening blob bucket for content: %w", err) + } + switch r, err := b.NewReader(ctx, key); { + case errors.Is(err, fs.ErrNotExist): + return nil, b.Close() + case err != nil: + return nil, errors.Join(fmt.Errorf("ent: creating reader for content: %w", err), b.Close()) + default: + return r, nil + } +} + +// ContentWriter opens a writer for the "content" field in blob storage. +// The caller must close the returned writer when done. +// Writing via this method does not go through the mutation pipeline. +func (_m *Document) ContentWriter(ctx context.Context) (io.WriteCloser, error) { + key, err := _m.ContentKey(ctx) + if err != nil { + return nil, fmt.Errorf("ent: blob key for content: %w", err) + } + b, err := _m.blobOpeners.Document(ctx, document.FieldContent) + if err != nil { + return nil, fmt.Errorf("ent: opening blob bucket for content: %w", err) + } + w, err := b.NewWriter(ctx, key) + if err != nil { + return nil, errors.Join(fmt.Errorf("ent: creating writer for content: %w", err), b.Close()) + } + return w, nil +} + +// ContentKey returns the blob storage key for the "content" field. +func (_m *Document) ContentKey(context.Context) (string, error) { + return fmt.Sprintf("%s/%v/%s", "documents", _m.ID, "content"), nil +} + +// Thumbnail opens a reader for the "thumbnail" field from blob storage. +// The caller must close the returned reader when done. +// It returns nil, nil if the blob does not exist. +func (_m *Document) Thumbnail(ctx context.Context) (io.ReadCloser, error) { + key, err := _m.ThumbnailKey(ctx) + if err != nil { + return nil, fmt.Errorf("ent: blob key for thumbnail: %w", err) + } + b, err := _m.blobOpeners.Document(ctx, document.FieldThumbnail) + if err != nil { + return nil, fmt.Errorf("ent: opening blob bucket for thumbnail: %w", err) + } + switch r, err := b.NewReader(ctx, key); { + case errors.Is(err, fs.ErrNotExist): + return nil, b.Close() + case err != nil: + return nil, errors.Join(fmt.Errorf("ent: creating reader for thumbnail: %w", err), b.Close()) + default: + return r, nil + } +} + +// ThumbnailWriter opens a writer for the "thumbnail" field in blob storage. +// The caller must close the returned writer when done. +// Writing via this method does not go through the mutation pipeline. +func (_m *Document) ThumbnailWriter(ctx context.Context) (io.WriteCloser, error) { + key, err := _m.ThumbnailKey(ctx) + if err != nil { + return nil, fmt.Errorf("ent: blob key for thumbnail: %w", err) + } + b, err := _m.blobOpeners.Document(ctx, document.FieldThumbnail) + if err != nil { + return nil, fmt.Errorf("ent: opening blob bucket for thumbnail: %w", err) + } + w, err := b.NewWriter(ctx, key) + if err != nil { + return nil, errors.Join(fmt.Errorf("ent: creating writer for thumbnail: %w", err), b.Close()) + } + return w, nil +} + +// ThumbnailKey returns a hash-based blob storage key for the "thumbnail" field. +func (_m *Document) ThumbnailKey(context.Context) (string, error) { + h := sha256.Sum256(fmt.Appendf(nil, "%s/%v/%s", "documents", _m.ID, "thumbnail")) + return hex.EncodeToString(h[:]), nil +} + +// Update returns a builder for updating this Document. +// Note that you need to call Document.Unwrap() before calling this method if this Document +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *Document) Update() *DocumentUpdateOne { + return NewDocumentClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the Document entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *Document) Unwrap() *Document { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: Document is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *Document) String() string { + var builder strings.Builder + builder.WriteString("Document(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("name=") + builder.WriteString(_m.Name) + builder.WriteByte(')') + return builder.String() +} + +// Documents is a parsable slice of Document. +type Documents []*Document + +// FromResponse scans the gremlin response data into Documents. +func (_m *Documents) FromResponse(res *gremlin.Response) error { + vmap, err := res.ReadValueMap() + if err != nil { + return err + } + var scan_m []struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Content io.Reader `json:"content,omitempty"` + Thumbnail io.Reader `json:"thumbnail,omitempty"` + } + if err := vmap.Decode(&scan_m); err != nil { + return err + } + for _, v := range scan_m { + node := &Document{ID: v.ID} + node.Name = v.Name + node.Content = v.Content + node.Thumbnail = v.Thumbnail + *_m = append(*_m, node) + } + return nil +} diff --git a/entc/integration/gremlin/ent/document/document.go b/entc/integration/gremlin/ent/document/document.go new file mode 100644 index 000000000..75cf25db4 --- /dev/null +++ b/entc/integration/gremlin/ent/document/document.go @@ -0,0 +1,29 @@ +// 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 by ent, DO NOT EDIT. + +package document + +import ( + "entgo.io/ent/dialect/gremlin/graph/dsl" +) + +const ( + // Label holds the string label denoting the document type in the database. + Label = "document" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldName holds the string denoting the name field in the database. + FieldName = "name" + // FieldContent holds the string denoting the content field in the database. + FieldContent = "content" + // FieldThumbnail holds the string denoting the thumbnail field in the database. + FieldThumbnail = "thumbnail" +) + +// OrderOption defines the ordering options for the Document queries. +type OrderOption func(*dsl.Traversal) + +// comment from another template. diff --git a/entc/integration/gremlin/ent/document/mutation.go b/entc/integration/gremlin/ent/document/mutation.go new file mode 100644 index 000000000..5ba895edf --- /dev/null +++ b/entc/integration/gremlin/ent/document/mutation.go @@ -0,0 +1,280 @@ +// 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 by ent, DO NOT EDIT. + +package document + +import ( + "context" + "fmt" + "io" + + "entgo.io/ent" + "entgo.io/ent/dialect/gremlin/graph/dsl" + "entgo.io/ent/entc/integration/gremlin/ent/predicate" +) + +// Mutation represents an operation that mutates the Document nodes in the graph. +type Mutation struct { + op ent.Op + typ string + name *string + content io.Reader + thumbnail io.Reader + clearedFields map[string]struct{} + predicates []predicate.Document +} + +// NewMutation creates a new Mutation for the Document entity. +func NewMutation(op ent.Op) *Mutation { + return &Mutation{ + op: op, + typ: "Document", + clearedFields: make(map[string]struct{}), + } +} + +// Predicates returns the list of predicates set on the mutation. +func (m *Mutation) Predicates() []predicate.Document { + return m.predicates +} + +// SetName sets the "name" field. +func (m *Mutation) SetName(s string) { + m.name = &s +} + +// Name returns the value of the "name" field in the mutation. +func (m *Mutation) Name() (r string, exists bool) { + v := m.name + if v == nil { + return + } + return *v, true +} + +// ResetName resets all changes to the "name" field. +func (m *Mutation) ResetName() { + m.name = nil +} + +// SetContent sets the "content" field. +func (m *Mutation) SetContent(r io.Reader) { + m.content = r +} + +// Content returns the value of the "content" field in the mutation. +func (m *Mutation) Content() (r io.Reader, exists bool) { + v := m.content + if v == nil { + return + } + return v, true +} + +// ResetContent resets all changes to the "content" field. +func (m *Mutation) ResetContent() { + m.content = nil +} + +// SetThumbnail sets the "thumbnail" field. +func (m *Mutation) SetThumbnail(r io.Reader) { + m.thumbnail = r +} + +// Thumbnail returns the value of the "thumbnail" field in the mutation. +func (m *Mutation) Thumbnail() (r io.Reader, exists bool) { + v := m.thumbnail + if v == nil { + return + } + return v, true +} + +// ResetThumbnail resets all changes to the "thumbnail" field. +func (m *Mutation) ResetThumbnail() { + m.thumbnail = nil +} + +// Where appends a list predicates to the Mutation builder. +func (m *Mutation) Where(ps ...predicate.Document) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the Mutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *Mutation) WhereP(ps ...func(*dsl.Traversal)) { + p := make([]predicate.Document, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *Mutation) Op() ent.Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *Mutation) SetOp(op ent.Op) { + m.op = op +} + +// Type returns the node type of this mutation (Document). +func (m *Mutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *Mutation) Fields() []string { + fields := make([]string, 0, 3) + if m.name != nil { + fields = append(fields, FieldName) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *Mutation) Field(name string) (ent.Value, bool) { + switch name { + case FieldName: + return m.Name() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *Mutation) OldField(ctx context.Context, name string) (ent.Value, error) { + return nil, fmt.Errorf("unknown Document field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *Mutation) SetField(name string, value ent.Value) error { + switch name { + case FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + } + return fmt.Errorf("unknown Document field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *Mutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *Mutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *Mutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown Document numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *Mutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *Mutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *Mutation) ClearField(name string) error { + return fmt.Errorf("unknown Document nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *Mutation) ResetField(name string) error { + switch name { + case FieldName: + m.ResetName() + return nil + case FieldContent: + m.ResetContent() + return nil + case FieldThumbnail: + m.ResetThumbnail() + return nil + } + return fmt.Errorf("unknown Document field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *Mutation) AddedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *Mutation) AddedIDs(name string) []ent.Value { + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *Mutation) RemovedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *Mutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *Mutation) ClearedEdges() []string { + edges := make([]string, 0, 0) + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *Mutation) EdgeCleared(name string) bool { + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *Mutation) ClearEdge(name string) error { + return fmt.Errorf("unknown Document unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *Mutation) ResetEdge(name string) error { + return fmt.Errorf("unknown Document edge %s", name) +} diff --git a/entc/integration/gremlin/ent/document/where.go b/entc/integration/gremlin/ent/document/where.go new file mode 100644 index 000000000..f6bf3c126 --- /dev/null +++ b/entc/integration/gremlin/ent/document/where.go @@ -0,0 +1,204 @@ +// 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 by ent, DO NOT EDIT. + +package document + +import ( + "entgo.io/ent/dialect/gremlin/graph/dsl" + "entgo.io/ent/dialect/gremlin/graph/dsl/__" + "entgo.io/ent/dialect/gremlin/graph/dsl/p" + "entgo.io/ent/entc/integration/gremlin/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.HasID(id) + }) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.HasID(p.EQ(id)) + }) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.HasID(p.NEQ(id)) + }) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + v := make([]any, len(ids)) + for i := range v { + v[i] = ids[i] + } + t.HasID(p.Within(v...)) + }) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + v := make([]any, len(ids)) + for i := range v { + v[i] = ids[i] + } + t.HasID(p.Without(v...)) + }) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.HasID(p.GT(id)) + }) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.HasID(p.GTE(id)) + }) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.HasID(p.LT(id)) + }) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.HasID(p.LTE(id)) + }) +} + +// Name applies equality check predicate on the "name" field. It's identical to NameEQ. +func Name(v string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.Has(Label, FieldName, p.EQ(v)) + }) +} + +// NameEQ applies the EQ predicate on the "name" field. +func NameEQ(v string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.Has(Label, FieldName, p.EQ(v)) + }) +} + +// NameNEQ applies the NEQ predicate on the "name" field. +func NameNEQ(v string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.Has(Label, FieldName, p.NEQ(v)) + }) +} + +// NameIn applies the In predicate on the "name" field. +func NameIn(vs ...string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.Has(Label, FieldName, p.Within(vs...)) + }) +} + +// NameNotIn applies the NotIn predicate on the "name" field. +func NameNotIn(vs ...string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.Has(Label, FieldName, p.Without(vs...)) + }) +} + +// NameGT applies the GT predicate on the "name" field. +func NameGT(v string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.Has(Label, FieldName, p.GT(v)) + }) +} + +// NameGTE applies the GTE predicate on the "name" field. +func NameGTE(v string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.Has(Label, FieldName, p.GTE(v)) + }) +} + +// NameLT applies the LT predicate on the "name" field. +func NameLT(v string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.Has(Label, FieldName, p.LT(v)) + }) +} + +// NameLTE applies the LTE predicate on the "name" field. +func NameLTE(v string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.Has(Label, FieldName, p.LTE(v)) + }) +} + +// NameContains applies the Contains predicate on the "name" field. +func NameContains(v string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.Has(Label, FieldName, p.Containing(v)) + }) +} + +// NameHasPrefix applies the HasPrefix predicate on the "name" field. +func NameHasPrefix(v string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.Has(Label, FieldName, p.StartingWith(v)) + }) +} + +// NameHasSuffix applies the HasSuffix predicate on the "name" field. +func NameHasSuffix(v string) predicate.Document { + return predicate.Document(func(t *dsl.Traversal) { + t.Has(Label, FieldName, p.EndingWith(v)) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.Document) predicate.Document { + return predicate.Document(func(tr *dsl.Traversal) { + trs := make([]any, 0, len(predicates)) + for _, p := range predicates { + t := __.New() + p(t) + trs = append(trs, t) + } + tr.Where(__.And(trs...)) + }) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.Document) predicate.Document { + return predicate.Document(func(tr *dsl.Traversal) { + trs := make([]any, 0, len(predicates)) + for _, p := range predicates { + t := __.New() + p(t) + trs = append(trs, t) + } + tr.Where(__.Or(trs...)) + }) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.Document) predicate.Document { + return predicate.Document(func(tr *dsl.Traversal) { + t := __.New() + p(t) + tr.Where(__.Not(t)) + }) +} diff --git a/entc/integration/gremlin/ent/document_create.go b/entc/integration/gremlin/ent/document_create.go new file mode 100644 index 000000000..cc1bc691f --- /dev/null +++ b/entc/integration/gremlin/ent/document_create.go @@ -0,0 +1,125 @@ +// 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 by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "io" + + "entgo.io/ent/dialect/gremlin" + "entgo.io/ent/dialect/gremlin/graph/dsl" + "entgo.io/ent/dialect/gremlin/graph/dsl/g" + "entgo.io/ent/entc/integration/gremlin/ent/document" +) + +// DocumentCreate is the builder for creating a Document entity. +type DocumentCreate struct { + config + mutation *DocumentMutation + hooks []Hook +} + +// SetName sets the "name" field. +func (_c *DocumentCreate) SetName(v string) *DocumentCreate { + _c.mutation.SetName(v) + return _c +} + +// SetContent sets the "content" field. +func (_c *DocumentCreate) SetContent(v io.Reader) *DocumentCreate { + _c.mutation.SetContent(v) + return _c +} + +// SetThumbnail sets the "thumbnail" field. +func (_c *DocumentCreate) SetThumbnail(v io.Reader) *DocumentCreate { + _c.mutation.SetThumbnail(v) + return _c +} + +// Mutation returns the DocumentMutation object of the builder. +func (_c *DocumentCreate) Mutation() *DocumentMutation { + return _c.mutation +} + +// Save creates the Document in the database. +func (_c *DocumentCreate) Save(ctx context.Context) (*Document, error) { + return withHooks(ctx, _c.gremlinSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *DocumentCreate) SaveX(ctx context.Context) *Document { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *DocumentCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *DocumentCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *DocumentCreate) check() error { + if _, ok := _c.mutation.Name(); !ok { + return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "Document.name"`)} + } + if _, ok := _c.mutation.Content(); !ok { + return &ValidationError{Name: "content", err: errors.New(`ent: missing required field "Document.content"`)} + } + if _, ok := _c.mutation.Thumbnail(); !ok { + return &ValidationError{Name: "thumbnail", err: errors.New(`ent: missing required field "Document.thumbnail"`)} + } + return nil +} + +func (_c *DocumentCreate) gremlinSave(ctx context.Context) (*Document, error) { + if err := _c.check(); err != nil { + return nil, err + } + res := &gremlin.Response{} + query, bindings := _c.gremlin().Query() + if err := _c.driver.Exec(ctx, query, bindings, res); err != nil { + return nil, err + } + if err, ok := isConstantError(res); ok { + return nil, err + } + rnode := &Document{config: _c.config} + if err := rnode.FromResponse(res); err != nil { + return nil, err + } + _c.mutation.id = &rnode.ID + _c.mutation.done = true + return rnode, nil +} + +func (_c *DocumentCreate) gremlin() *dsl.Traversal { + v := g.AddV(document.Label) + if value, ok := _c.mutation.Name(); ok { + v.Property(dsl.Single, document.FieldName, value) + } + return v.ValueMap(true) +} + +// DocumentCreateBulk is the builder for creating many Document entities in bulk. +type DocumentCreateBulk struct { + config + err error + builders []*DocumentCreate +} diff --git a/entc/integration/gremlin/ent/document_delete.go b/entc/integration/gremlin/ent/document_delete.go new file mode 100644 index 000000000..4dc81b27c --- /dev/null +++ b/entc/integration/gremlin/ent/document_delete.go @@ -0,0 +1,94 @@ +// 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 by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/gremlin" + "entgo.io/ent/dialect/gremlin/graph/dsl" + "entgo.io/ent/dialect/gremlin/graph/dsl/__" + "entgo.io/ent/dialect/gremlin/graph/dsl/g" + "entgo.io/ent/entc/integration/gremlin/ent/document" + "entgo.io/ent/entc/integration/gremlin/ent/predicate" +) + +// DocumentDelete is the builder for deleting a Document entity. +type DocumentDelete struct { + config + hooks []Hook + mutation *DocumentMutation +} + +// Where appends a list predicates to the DocumentDelete builder. +func (_d *DocumentDelete) Where(ps ...predicate.Document) *DocumentDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *DocumentDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.gremlinExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *DocumentDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *DocumentDelete) gremlinExec(ctx context.Context) (int, error) { + res := &gremlin.Response{} + query, bindings := _d.gremlin().Query() + if err := _d.driver.Exec(ctx, query, bindings, res); err != nil { + return 0, err + } + _d.mutation.done = true + return res.ReadInt() +} + +func (_d *DocumentDelete) gremlin() *dsl.Traversal { + t := g.V().HasLabel(document.Label) + for _, p := range _d.mutation.Predicates() { + p(t) + } + return t.SideEffect(__.Drop()).Count() +} + +// DocumentDeleteOne is the builder for deleting a single Document entity. +type DocumentDeleteOne struct { + _d *DocumentDelete +} + +// Where appends a list predicates to the DocumentDelete builder. +func (_d *DocumentDeleteOne) Where(ps ...predicate.Document) *DocumentDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *DocumentDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{document.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *DocumentDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/entc/integration/gremlin/ent/document_query.go b/entc/integration/gremlin/ent/document_query.go new file mode 100644 index 000000000..2eebe52c1 --- /dev/null +++ b/entc/integration/gremlin/ent/document_query.go @@ -0,0 +1,502 @@ +// 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 by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect/gremlin" + "entgo.io/ent/dialect/gremlin/graph/dsl" + "entgo.io/ent/dialect/gremlin/graph/dsl/__" + "entgo.io/ent/dialect/gremlin/graph/dsl/g" + "entgo.io/ent/entc/integration/gremlin/ent/document" + "entgo.io/ent/entc/integration/gremlin/ent/predicate" +) + +// DocumentQuery is the builder for querying Document entities. +type DocumentQuery struct { + config + ctx *QueryContext + order []document.OrderOption + inters []Interceptor + predicates []predicate.Document + // intermediate query (i.e. traversal path). + gremlin *dsl.Traversal + path func(context.Context) (*dsl.Traversal, error) +} + +// Where adds a new predicate for the DocumentQuery builder. +func (_q *DocumentQuery) Where(ps ...predicate.Document) *DocumentQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *DocumentQuery) Limit(limit int) *DocumentQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *DocumentQuery) Offset(offset int) *DocumentQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *DocumentQuery) Unique(unique bool) *DocumentQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *DocumentQuery) Order(o ...document.OrderOption) *DocumentQuery { + _q.order = append(_q.order, o...) + return _q +} + +// First returns the first Document entity from the query. +// Returns a *NotFoundError when no Document was found. +func (_q *DocumentQuery) First(ctx context.Context) (*Document, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{document.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *DocumentQuery) FirstX(ctx context.Context) *Document { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first Document ID from the query. +// Returns a *NotFoundError when no Document ID was found. +func (_q *DocumentQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{document.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *DocumentQuery) FirstIDX(ctx context.Context) string { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single Document entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one Document entity is found. +// Returns a *NotFoundError when no Document entities are found. +func (_q *DocumentQuery) Only(ctx context.Context) (*Document, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{document.Label} + default: + return nil, &NotSingularError{document.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *DocumentQuery) OnlyX(ctx context.Context) *Document { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only Document ID in the query. +// Returns a *NotSingularError when more than one Document ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *DocumentQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{document.Label} + default: + err = &NotSingularError{document.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *DocumentQuery) OnlyIDX(ctx context.Context) string { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of Documents. +func (_q *DocumentQuery) All(ctx context.Context) ([]*Document, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*Document, *DocumentQuery]() + return withInterceptors[[]*Document](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *DocumentQuery) AllX(ctx context.Context) []*Document { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of Document IDs. +func (_q *DocumentQuery) IDs(ctx context.Context) (ids []string, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(document.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *DocumentQuery) IDsX(ctx context.Context) []string { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *DocumentQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*DocumentQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *DocumentQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *DocumentQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *DocumentQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the DocumentQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *DocumentQuery) Clone() *DocumentQuery { + if _q == nil { + return nil + } + return &DocumentQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]document.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.Document{}, _q.predicates...), + // clone intermediate query. + gremlin: _q.gremlin.Clone(), + path: _q.path, + } +} + +// GroupBy is 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.Document.Query(). +// GroupBy(document.FieldName). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *DocumentQuery) GroupBy(field string, fields ...string) *DocumentGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &DocumentGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = document.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// Name string `json:"name,omitempty"` +// } +// +// client.Document.Query(). +// Select(document.FieldName). +// Scan(ctx, &v) +func (_q *DocumentQuery) Select(fields ...string) *DocumentSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &DocumentSelect{DocumentQuery: _q} + sbuild.label = document.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a DocumentSelect configured with the given aggregations. +func (_q *DocumentQuery) Aggregate(fns ...AggregateFunc) *DocumentSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *DocumentQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.gremlin = prev + } + return nil +} + +func (_q *DocumentQuery) gremlinAll(ctx context.Context, hooks ...queryHook) ([]*Document, error) { + res := &gremlin.Response{} + traversal := _q.gremlinQuery(ctx) + if len(_q.ctx.Fields) > 0 { + fields := make([]any, len(_q.ctx.Fields)) + for i, f := range _q.ctx.Fields { + fields[i] = f + } + traversal.ValueMap(fields...) + } else { + traversal.ValueMap(true) + } + query, bindings := traversal.Query() + if err := _q.driver.Exec(ctx, query, bindings, res); err != nil { + return nil, err + } + var _ms Documents + if err := _ms.FromResponse(res); err != nil { + return nil, err + } + for i := range _ms { + _ms[i].config = _q.config + } + return _ms, nil +} + +func (_q *DocumentQuery) gremlinCount(ctx context.Context) (int, error) { + res := &gremlin.Response{} + query, bindings := _q.gremlinQuery(ctx).Count().Query() + if err := _q.driver.Exec(ctx, query, bindings, res); err != nil { + return 0, err + } + return res.ReadInt() +} + +func (_q *DocumentQuery) gremlinQuery(context.Context) *dsl.Traversal { + v := g.V().HasLabel(document.Label) + if _q.gremlin != nil { + v = _q.gremlin.Clone() + } + for _, p := range _q.predicates { + p(v) + } + if len(_q.order) > 0 { + v.Order() + for _, p := range _q.order { + p(v) + } + } + switch limit, offset := _q.ctx.Limit, _q.ctx.Offset; { + case limit != nil && offset != nil: + v.Range(*offset, *offset+*limit) + case offset != nil: + v.Range(*offset, math.MaxInt32) + case limit != nil: + v.Limit(*limit) + } + if unique := _q.ctx.Unique; unique == nil || *unique { + v.Dedup() + } + return v +} + +// DocumentGroupBy is the group-by builder for Document entities. +type DocumentGroupBy struct { + selector + build *DocumentQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *DocumentGroupBy) Aggregate(fns ...AggregateFunc) *DocumentGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *DocumentGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*DocumentQuery, *DocumentGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *DocumentGroupBy) gremlinScan(ctx context.Context, root *DocumentQuery, v any) error { + var ( + trs []any + names []any + ) + for _, fn := range _g.fns { + name, tr := fn("p", "") + trs = append(trs, tr) + names = append(names, name) + } + for _, f := range *_g.flds { + names = append(names, f) + trs = append(trs, __.As("p").Unfold().Values(f).As(f)) + } + query, bindings := root.gremlinQuery(ctx).Group(). + By(__.Values(*_g.flds...).Fold()). + By(__.Fold().Match(trs...).Select(names...)). + Select(dsl.Values). + Next(). + Query() + res := &gremlin.Response{} + if err := _g.build.driver.Exec(ctx, query, bindings, res); err != nil { + return err + } + if len(*_g.flds)+len(_g.fns) == 1 { + return res.ReadVal(v) + } + vm, err := res.ReadValueMap() + if err != nil { + return err + } + return vm.Decode(v) +} + +// DocumentSelect is the builder for selecting fields of Document entities. +type DocumentSelect struct { + *DocumentQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *DocumentSelect) Aggregate(fns ...AggregateFunc) *DocumentSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *DocumentSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*DocumentQuery, *DocumentSelect](ctx, _s.DocumentQuery, _s, _s.inters, v) +} + +func (_s *DocumentSelect) gremlinScan(ctx context.Context, root *DocumentQuery, v any) error { + var ( + res = &gremlin.Response{} + traversal = root.gremlinQuery(ctx) + ) + if fields := _s.ctx.Fields; len(fields) == 1 { + if fields[0] != document.FieldID { + traversal = traversal.Values(fields...) + } else { + traversal = traversal.ID() + } + } else { + fields := make([]any, len(_s.ctx.Fields)) + for i, f := range _s.ctx.Fields { + fields[i] = f + } + traversal = traversal.ValueMap(fields...) + } + query, bindings := traversal.Query() + if err := _s.driver.Exec(ctx, query, bindings, res); err != nil { + return err + } + if len(root.ctx.Fields) == 1 { + return res.ReadVal(v) + } + vm, err := res.ReadValueMap() + if err != nil { + return err + } + return vm.Decode(v) +} diff --git a/entc/integration/gremlin/ent/document_update.go b/entc/integration/gremlin/ent/document_update.go new file mode 100644 index 000000000..7c41c8882 --- /dev/null +++ b/entc/integration/gremlin/ent/document_update.go @@ -0,0 +1,241 @@ +// 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 by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "io" + + "entgo.io/ent/dialect/gremlin" + "entgo.io/ent/dialect/gremlin/graph/dsl" + "entgo.io/ent/dialect/gremlin/graph/dsl/g" + "entgo.io/ent/entc/integration/gremlin/ent/document" + "entgo.io/ent/entc/integration/gremlin/ent/predicate" +) + +// DocumentUpdate is the builder for updating Document entities. +type DocumentUpdate struct { + config + hooks []Hook + mutation *DocumentMutation +} + +// Where appends a list predicates to the DocumentUpdate builder. +func (_u *DocumentUpdate) Where(ps ...predicate.Document) *DocumentUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetName sets the "name" field. +func (_u *DocumentUpdate) SetName(v string) *DocumentUpdate { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *DocumentUpdate) SetNillableName(v *string) *DocumentUpdate { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetContent sets the "content" field. +func (_u *DocumentUpdate) SetContent(v io.Reader) *DocumentUpdate { + _u.mutation.SetContent(v) + return _u +} + +// SetThumbnail sets the "thumbnail" field. +func (_u *DocumentUpdate) SetThumbnail(v io.Reader) *DocumentUpdate { + _u.mutation.SetThumbnail(v) + return _u +} + +// Mutation returns the DocumentMutation object of the builder. +func (_u *DocumentUpdate) Mutation() *DocumentMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *DocumentUpdate) Save(ctx context.Context) (int, error) { + return withHooks(ctx, _u.gremlinSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *DocumentUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *DocumentUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *DocumentUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +func (_u *DocumentUpdate) gremlinSave(ctx context.Context) (int, error) { + res := &gremlin.Response{} + query, bindings := _u.gremlin().Query() + if err := _u.driver.Exec(ctx, query, bindings, res); err != nil { + return 0, err + } + if err, ok := isConstantError(res); ok { + return 0, err + } + _u.mutation.done = true + return res.ReadInt() +} + +func (_u *DocumentUpdate) gremlin() *dsl.Traversal { + v := g.V().HasLabel(document.Label) + for _, p := range _u.mutation.Predicates() { + p(v) + } + var ( + trs []*dsl.Traversal + ) + if value, ok := _u.mutation.Name(); ok { + v.Property(dsl.Single, document.FieldName, value) + } + v.Count() + trs = append(trs, v) + return dsl.Join(trs...) +} + +// DocumentUpdateOne is the builder for updating a single Document entity. +type DocumentUpdateOne struct { + config + fields []string + hooks []Hook + mutation *DocumentMutation +} + +// SetName sets the "name" field. +func (_u *DocumentUpdateOne) SetName(v string) *DocumentUpdateOne { + _u.mutation.SetName(v) + return _u +} + +// SetNillableName sets the "name" field if the given value is not nil. +func (_u *DocumentUpdateOne) SetNillableName(v *string) *DocumentUpdateOne { + if v != nil { + _u.SetName(*v) + } + return _u +} + +// SetContent sets the "content" field. +func (_u *DocumentUpdateOne) SetContent(v io.Reader) *DocumentUpdateOne { + _u.mutation.SetContent(v) + return _u +} + +// SetThumbnail sets the "thumbnail" field. +func (_u *DocumentUpdateOne) SetThumbnail(v io.Reader) *DocumentUpdateOne { + _u.mutation.SetThumbnail(v) + return _u +} + +// Mutation returns the DocumentMutation object of the builder. +func (_u *DocumentUpdateOne) Mutation() *DocumentMutation { + return _u.mutation +} + +// Where appends a list predicates to the DocumentUpdate builder. +func (_u *DocumentUpdateOne) Where(ps ...predicate.Document) *DocumentUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *DocumentUpdateOne) Select(field string, fields ...string) *DocumentUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated Document entity. +func (_u *DocumentUpdateOne) Save(ctx context.Context) (*Document, error) { + return withHooks(ctx, _u.gremlinSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *DocumentUpdateOne) SaveX(ctx context.Context) *Document { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *DocumentUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *DocumentUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +func (_u *DocumentUpdateOne) gremlinSave(ctx context.Context) (*Document, error) { + res := &gremlin.Response{} + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "Document.id" for update`)} + } + query, bindings := _u.gremlin(id).Query() + if err := _u.driver.Exec(ctx, query, bindings, res); err != nil { + return nil, err + } + if err, ok := isConstantError(res); ok { + return nil, err + } + _u.mutation.done = true + _m := &Document{config: _u.config} + if err := _m.FromResponse(res); err != nil { + return nil, err + } + return _m, nil +} + +func (_u *DocumentUpdateOne) gremlin(id string) *dsl.Traversal { + v := g.V(id) + var ( + trs []*dsl.Traversal + ) + if value, ok := _u.mutation.Name(); ok { + v.Property(dsl.Single, document.FieldName, value) + } + if len(_u.fields) > 0 { + fields := make([]any, 0, len(_u.fields)+1) + fields = append(fields, true) + for _, f := range _u.fields { + fields = append(fields, f) + } + v.ValueMap(fields...) + } else { + v.ValueMap(true) + } + trs = append(trs, v) + return dsl.Join(trs...) +} diff --git a/entc/integration/gremlin/ent/hook/hook.go b/entc/integration/gremlin/ent/hook/hook.go index 5f9210af2..9b049f539 100644 --- a/entc/integration/gremlin/ent/hook/hook.go +++ b/entc/integration/gremlin/ent/hook/hook.go @@ -61,6 +61,18 @@ func (f CommentFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, err return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.CommentMutation", m) } +// The DocumentFunc type is an adapter to allow the use of ordinary +// function as Document mutator. +type DocumentFunc func(context.Context, *ent.DocumentMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f DocumentFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.DocumentMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.DocumentMutation", m) +} + // The ExValueScanFunc type is an adapter to allow the use of ordinary // function as ExValueScan mutator. type ExValueScanFunc func(context.Context, *ent.ExValueScanMutation) (ent.Value, error) diff --git a/entc/integration/gremlin/ent/mutation.go b/entc/integration/gremlin/ent/mutation.go index f2e942791..6b85fdad3 100644 --- a/entc/integration/gremlin/ent/mutation.go +++ b/entc/integration/gremlin/ent/mutation.go @@ -27,6 +27,7 @@ import ( "entgo.io/ent/entc/integration/gremlin/ent/builder" "entgo.io/ent/entc/integration/gremlin/ent/card" "entgo.io/ent/entc/integration/gremlin/ent/comment" + "entgo.io/ent/entc/integration/gremlin/ent/document" "entgo.io/ent/entc/integration/gremlin/ent/exvaluescan" "entgo.io/ent/entc/integration/gremlin/ent/fieldtype" "entgo.io/ent/entc/integration/gremlin/ent/file" @@ -58,6 +59,7 @@ const ( TypeBuilder = "Builder" TypeCard = "Card" TypeComment = "Comment" + TypeDocument = "Document" TypeExValueScan = "ExValueScan" TypeFieldType = "FieldType" TypeFile = "File" @@ -736,6 +738,139 @@ func (m *CommentMutation) OldField(ctx context.Context, name string) (ent.Value, return nil, fmt.Errorf("unknown Comment field %s", name) } +// DocumentMutation represents an operation that mutates the Document nodes in the graph. +type DocumentMutation struct { + document.Mutation + config + id *string + done bool + oldValue func(context.Context) (*Document, error) +} + +var _ ent.Mutation = (*DocumentMutation)(nil) + +// documentOption allows management of the mutation configuration using functional options. +type documentOption func(*DocumentMutation) + +// newDocumentMutation creates new mutation for the Document entity. +func newDocumentMutation(c config, op Op, opts ...documentOption) *DocumentMutation { + m := &DocumentMutation{ + Mutation: *document.NewMutation(op), + config: c, + } + for _, opt := range opts { + opt(m) + } + return m +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *DocumentMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// withDocumentID sets the ID field of the mutation. +func withDocumentID(id string) documentOption { + return func(m *DocumentMutation) { + var ( + err error + once sync.Once + value *Document + ) + m.oldValue = func(ctx context.Context) (*Document, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().Document.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withDocument sets the old Document of the mutation. +func withDocument(node *Document) documentOption { + return func(m *DocumentMutation) { + m.oldValue = func(context.Context) (*Document, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m DocumentMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m DocumentMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *DocumentMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.Op().Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.Op().Is(OpUpdate | OpDelete): + return m.Client().Document.Query().Where(m.Predicates()...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.Op()) + } +} + +// OldName returns the old "name" field's value of the Document entity. +// If the Document object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *DocumentMutation) OldName(ctx context.Context) (v string, err error) { + if !m.Op().Is(OpUpdateOne) { + return v, errors.New("OldName is only allowed on UpdateOne operations") + } + if _, exists := m.ID(); !exists || m.oldValue == nil { + return v, errors.New("OldName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldName: %w", err) + } + return oldValue.Name, nil +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *DocumentMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case document.FieldName: + return m.OldName(ctx) + } + return nil, fmt.Errorf("unknown Document field %s", name) +} + // ExValueScanMutation represents an operation that mutates the ExValueScan nodes in the graph. type ExValueScanMutation struct { exvaluescan.Mutation diff --git a/entc/integration/gremlin/ent/predicate/predicate.go b/entc/integration/gremlin/ent/predicate/predicate.go index 58cad4d3d..abeb8c8c2 100644 --- a/entc/integration/gremlin/ent/predicate/predicate.go +++ b/entc/integration/gremlin/ent/predicate/predicate.go @@ -22,6 +22,9 @@ type Card func(*dsl.Traversal) // Comment is the predicate function for comment builders. type Comment func(*dsl.Traversal) +// Document is the predicate function for document builders. +type Document func(*dsl.Traversal) + // ExValueScan is the predicate function for exvaluescan builders. type ExValueScan func(*dsl.Traversal) diff --git a/entc/integration/gremlin/ent/runtime.go b/entc/integration/gremlin/ent/runtime.go index f5a0d7269..856edba36 100644 --- a/entc/integration/gremlin/ent/runtime.go +++ b/entc/integration/gremlin/ent/runtime.go @@ -63,6 +63,8 @@ func init() { cardDescName := cardFields[2].Descriptor() // card.NameValidator is a validator for the "name" field. It is called by the builders before save. card.NameValidator = cardDescName.Validators[0].(func(string) error) + documentFields := schema.Document{}.Fields() + _ = documentFields exvaluescanFields := schema.ExValueScan{}.Fields() _ = exvaluescanFields // exvaluescanDescBinary is the schema descriptor for binary field. diff --git a/entc/integration/gremlin/ent/tx.go b/entc/integration/gremlin/ent/tx.go index 0a26cc409..7462c1872 100644 --- a/entc/integration/gremlin/ent/tx.go +++ b/entc/integration/gremlin/ent/tx.go @@ -24,6 +24,8 @@ type Tx struct { Card *CardClient // Comment is the client for interacting with the Comment builders. Comment *CommentClient + // Document is the client for interacting with the Document builders. + Document *DocumentClient // ExValueScan is the client for interacting with the ExValueScan builders. ExValueScan *ExValueScanClient // FieldType is the client for interacting with the FieldType builders. @@ -189,6 +191,7 @@ func (tx *Tx) init() { tx.Builder = NewBuilderClient(tx.config) tx.Card = NewCardClient(tx.config) tx.Comment = NewCommentClient(tx.config) + tx.Document = NewDocumentClient(tx.config) tx.ExValueScan = NewExValueScanClient(tx.config) tx.FieldType = NewFieldTypeClient(tx.config) tx.File = NewFileClient(tx.config) diff --git a/entc/integration/hooks/ent/internal/schema.go b/entc/integration/hooks/ent/internal/schema.go index 858d88348..f569d6377 100644 --- a/entc/integration/hooks/ent/internal/schema.go +++ b/entc/integration/hooks/ent/internal/schema.go @@ -10,4 +10,4 @@ // Package internal holds a loadable version of the latest schema. package internal -const Schema = "{\"Schema\":\"entgo.io/ent/entc/integration/hooks/ent/schema\",\"Package\":\"entgo.io/ent/entc/integration/hooks/ent\",\"Schemas\":[{\"name\":\"Card\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"owner\",\"type\":\"User\",\"ref_name\":\"cards\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"number\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"unknown\",\"default_kind\":24,\"immutable\":true,\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Exact name written on card\"},{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"in_hook\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"InHook is a mandatory field that is set by the hook.\"},{\"name\":\"expired_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]},{\"name\":\"Pet\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"owner\",\"type\":\"User\",\"ref_name\":\"pets\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"delete_time\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"User\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"cards\",\"type\":\"Card\"},{\"name\":\"pets\",\"type\":\"Pet\"},{\"name\":\"friends\",\"type\":\"User\"},{\"name\":\"best_friend\",\"type\":\"User\",\"unique\":true}],\"fields\":[{\"name\":\"version\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"worth\",\"type\":{\"Type\":17,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"password\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"active\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":true,\"default_kind\":1,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]}],\"Features\":[\"intercept\",\"schema/snapshot\"]}" +const Schema = "{\"Schema\":\"entgo.io/ent/entc/integration/hooks/ent/schema\",\"Package\":\"entgo.io/ent/entc/integration/hooks/ent\",\"Schemas\":[{\"name\":\"Card\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"owner\",\"type\":\"User\",\"ref_name\":\"cards\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"number\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"unknown\",\"default_kind\":24,\"immutable\":true,\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Exact name written on card\"},{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"in_hook\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"InHook is a mandatory field that is set by the hook.\"},{\"name\":\"expired_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]},{\"name\":\"Pet\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"owner\",\"type\":\"User\",\"ref_name\":\"pets\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"delete_time\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}]},{\"name\":\"User\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"cards\",\"type\":\"Card\"},{\"name\":\"pets\",\"type\":\"Pet\"},{\"name\":\"friends\",\"type\":\"User\"},{\"name\":\"best_friend\",\"type\":\"User\",\"unique\":true}],\"fields\":[{\"name\":\"version\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"worth\",\"type\":{\"Type\":18,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"password\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"active\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":true,\"default_kind\":1,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]}],\"Features\":[\"intercept\",\"schema/snapshot\"]}" diff --git a/entc/integration/privacy/ent/internal/schema.go b/entc/integration/privacy/ent/internal/schema.go index 62a19f79f..a7df04d03 100644 --- a/entc/integration/privacy/ent/internal/schema.go +++ b/entc/integration/privacy/ent/internal/schema.go @@ -10,4 +10,4 @@ // Package internal holds a loadable version of the latest schema. package internal -const Schema = "{\"Schema\":\"entgo.io/ent/entc/integration/privacy/ent/schema\",\"Package\":\"entgo.io/ent/entc/integration/privacy/ent\",\"Schemas\":[{\"name\":\"Task\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"teams\",\"type\":\"Team\"},{\"name\":\"owner\",\"type\":\"User\",\"ref_name\":\"tasks\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"title\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"task.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"planned\",\"V\":\"planned\"},{\"N\":\"in_progress\",\"V\":\"in_progress\"},{\"N\":\"closed\",\"V\":\"closed\"}],\"default\":true,\"default_value\":\"planned\",\"default_kind\":24,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"uuid\",\"type\":{\"Type\":4,\"Ident\":\"uuid.UUID\",\"PkgPath\":\"github.com/google/uuid\",\"PkgName\":\"uuid\",\"Nillable\":false,\"RType\":{\"Name\":\"UUID\",\"Ident\":\"uuid.UUID\",\"Kind\":17,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":{\"ClockSequence\":{\"In\":[],\"Out\":[{\"Name\":\"int\",\"Ident\":\"int\",\"Kind\":2,\"PkgPath\":\"\",\"Methods\":null}]},\"Domain\":{\"In\":[],\"Out\":[{\"Name\":\"Domain\",\"Ident\":\"uuid.Domain\",\"Kind\":8,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]},\"ID\":{\"In\":[],\"Out\":[{\"Name\":\"uint32\",\"Ident\":\"uint32\",\"Kind\":10,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalText\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"NodeID\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"Time\":{\"In\":[],\"Out\":[{\"Name\":\"Time\",\"Ident\":\"uuid.Time\",\"Kind\":6,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]},\"URN\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalText\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Variant\":{\"In\":[],\"Out\":[{\"Name\":\"Variant\",\"Ident\":\"uuid.Variant\",\"Kind\":8,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]},\"Version\":{\"In\":[],\"Out\":[{\"Name\":\"Version\",\"Ident\":\"uuid.Version\",\"Kind\":8,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]}}}},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"policy\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]},{\"name\":\"Team\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"tasks\",\"type\":\"Task\",\"ref_name\":\"teams\",\"inverse\":true},{\"name\":\"users\",\"type\":\"User\",\"ref_name\":\"teams\",\"inverse\":true}],\"fields\":[{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}],\"policy\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]},{\"name\":\"User\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"teams\",\"type\":\"Team\"},{\"name\":\"tasks\",\"type\":\"Task\"}],\"fields\":[{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"immutable\":true,\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"age\",\"type\":{\"Type\":17,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}}],\"policy\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntSQL\":{\"checks\":{\"backticks\":\"`name` IS NOT NULL\"}}}}],\"Features\":[\"privacy\",\"entql\",\"schema/snapshot\"]}" +const Schema = "{\"Schema\":\"entgo.io/ent/entc/integration/privacy/ent/schema\",\"Package\":\"entgo.io/ent/entc/integration/privacy/ent\",\"Schemas\":[{\"name\":\"Task\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"teams\",\"type\":\"Team\"},{\"name\":\"owner\",\"type\":\"User\",\"ref_name\":\"tasks\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"title\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"task.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"planned\",\"V\":\"planned\"},{\"N\":\"in_progress\",\"V\":\"in_progress\"},{\"N\":\"closed\",\"V\":\"closed\"}],\"default\":true,\"default_value\":\"planned\",\"default_kind\":24,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"uuid\",\"type\":{\"Type\":4,\"Ident\":\"uuid.UUID\",\"PkgPath\":\"github.com/google/uuid\",\"PkgName\":\"uuid\",\"Nillable\":false,\"RType\":{\"Name\":\"UUID\",\"Ident\":\"uuid.UUID\",\"Kind\":17,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":{\"ClockSequence\":{\"In\":[],\"Out\":[{\"Name\":\"int\",\"Ident\":\"int\",\"Kind\":2,\"PkgPath\":\"\",\"Methods\":null}]},\"Domain\":{\"In\":[],\"Out\":[{\"Name\":\"Domain\",\"Ident\":\"uuid.Domain\",\"Kind\":8,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]},\"ID\":{\"In\":[],\"Out\":[{\"Name\":\"uint32\",\"Ident\":\"uint32\",\"Kind\":10,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalBinary\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"MarshalText\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"NodeID\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"Scan\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"String\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"Time\":{\"In\":[],\"Out\":[{\"Name\":\"Time\",\"Ident\":\"uuid.Time\",\"Kind\":6,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]},\"URN\":{\"In\":[],\"Out\":[{\"Name\":\"string\",\"Ident\":\"string\",\"Kind\":24,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalBinary\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalText\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Value\":{\"In\":[],\"Out\":[{\"Name\":\"Value\",\"Ident\":\"driver.Value\",\"Kind\":20,\"PkgPath\":\"database/sql/driver\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"Variant\":{\"In\":[],\"Out\":[{\"Name\":\"Variant\",\"Ident\":\"uuid.Variant\",\"Kind\":8,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]},\"Version\":{\"In\":[],\"Out\":[{\"Name\":\"Version\",\"Ident\":\"uuid.Version\",\"Kind\":8,\"PkgPath\":\"github.com/google/uuid\",\"Methods\":null}]}}}},\"optional\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}}],\"hooks\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"policy\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]},{\"name\":\"Team\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"tasks\",\"type\":\"Task\",\"ref_name\":\"teams\",\"inverse\":true},{\"name\":\"users\",\"type\":\"User\",\"ref_name\":\"teams\",\"inverse\":true}],\"fields\":[{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}}],\"policy\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]},{\"name\":\"User\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"teams\",\"type\":\"Team\"},{\"name\":\"tasks\",\"type\":\"Task\"}],\"fields\":[{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"immutable\":true,\"validators\":1,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"age\",\"type\":{\"Type\":18,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}}],\"policy\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntSQL\":{\"checks\":{\"backticks\":\"`name` IS NOT NULL\"}}}}],\"Features\":[\"privacy\",\"entql\",\"schema/snapshot\"]}" diff --git a/entc/load/schema.go b/entc/load/schema.go index 9b659196e..e11a33a1a 100644 --- a/entc/load/schema.go +++ b/entc/load/schema.go @@ -63,7 +63,6 @@ type Field struct { Comment string `json:"comment,omitempty"` Deprecated bool `json:"deprecated,omitempty"` DeprecatedReason string `json:"deprecated_reason,omitempty"` - Blob bool `json:"blob,omitempty"` } // Edge represents an ent.Edge that was loaded from a complied user package. @@ -145,7 +144,6 @@ func NewField(fd *field.Descriptor) (*Field, error) { Comment: fd.Comment, Deprecated: fd.Deprecated, DeprecatedReason: fd.DeprecatedReason, - Blob: fd.Blob, } for _, at := range fd.Annotations { sf.addAnnotation(at) diff --git a/entc/load/schema_test.go b/entc/load/schema_test.go index 328c62574..97b19c882 100644 --- a/entc/load/schema_test.go +++ b/entc/load/schema_test.go @@ -351,9 +351,9 @@ type BlobDoc struct { func (BlobDoc) Fields() []ent.Field { return []ent.Field{ - field.Bytes("content").Blob(). + field.Blob("content"). Comment("blob content"), - field.Bytes("thumbnail").Blob(), + field.Blob("thumbnail"), } } @@ -374,23 +374,20 @@ func TestMarshalBlobSchema(t *testing.T) { // First blob field: content. f0 := s.Fields[0] require.Equal(t, "content", f0.Name) - require.Equal(t, field.TypeBytes, f0.Info.Type) - require.True(t, f0.Optional) + require.Equal(t, field.TypeBlob, f0.Info.Type) require.Equal(t, "blob content", f0.Comment) - require.True(t, f0.Blob, "content field should be blob-stored") // Second blob field: thumbnail. f1 := s.Fields[1] require.Equal(t, "thumbnail", f1.Name) - require.Equal(t, field.TypeBytes, f1.Info.Type) - require.True(t, f1.Blob, "thumbnail field should be blob-stored") + require.Equal(t, field.TypeBlob, f1.Info.Type) - // Verify JSON roundtrip preserves Blob. + // Verify JSON roundtrip preserves type. buf2, err := json.Marshal(s) require.NoError(t, err) s2 := &Schema{} require.NoError(t, json.Unmarshal(buf2, s2)) - require.True(t, s2.Fields[0].Blob) + require.Equal(t, field.TypeBlob, s2.Fields[0].Info.Type) } type TimeMixin struct { diff --git a/schema/field/field.go b/schema/field/field.go index 569a52213..3e52faef1 100644 --- a/schema/field/field.go +++ b/schema/field/field.go @@ -183,6 +183,22 @@ func Other(name string, typ driver.Valuer) *otherBuilder { return ob } +// Blob returns a new Field with type blob. Blob fields store their data in +// external blob storage rather than in the database. The mutation accepts an +// io.Reader for writing, and the model provides methods that return io.ReadCloser +// and io.WriteCloser for streaming access. +// +// field.Blob("content") +// +// field.Blob("avatar"). +// Optional() +func Blob(name string) *blobBuilder { + return &blobBuilder{&Descriptor{ + Name: name, + Info: &TypeInfo{Type: TypeBlob}, + }} +} + // stringBuilder is the builder for string fields. type stringBuilder struct { desc *Descriptor @@ -660,13 +676,6 @@ func (b *bytesBuilder) Optional() *bytesBuilder { return b } -// Blob indicates that this field stores its data in external blob storage -// rather than in the database. Blob fields have no SQL column. -func (b *bytesBuilder) Blob() *bytesBuilder { - b.desc.Blob = true - return b -} - // Sensitive fields not printable and not serializable. func (b *bytesBuilder) Sensitive() *bytesBuilder { b.desc.Sensitive = true @@ -1424,6 +1433,63 @@ func (b *otherBuilder) Descriptor() *Descriptor { return b.desc } +// blobBuilder is the builder for blob fields. +type blobBuilder struct { + desc *Descriptor +} + +// Optional indicates that this field is optional on create. +// Unlike edges, fields are required by default. +func (b *blobBuilder) Optional() *blobBuilder { + b.desc.Optional = true + return b +} + +// Immutable indicates that this field cannot be updated. +func (b *blobBuilder) Immutable() *blobBuilder { + b.desc.Immutable = true + return b +} + +// Comment sets the comment of the field. +func (b *blobBuilder) Comment(c string) *blobBuilder { + b.desc.Comment = c + return b +} + +// StructTag sets the struct tag of the field. +func (b *blobBuilder) StructTag(s string) *blobBuilder { + b.desc.Tag = s + return b +} + +// StorageKey sets the storage key of the field. +func (b *blobBuilder) StorageKey(key string) *blobBuilder { + b.desc.StorageKey = key + return b +} + +// Annotations adds a list of annotations to the field object to be used by +// codegen extensions. +func (b *blobBuilder) Annotations(annotations ...schema.Annotation) *blobBuilder { + b.desc.Annotations = append(b.desc.Annotations, annotations...) + return b +} + +// Deprecated marks the field as deprecated. +func (b *blobBuilder) Deprecated(reason ...string) *blobBuilder { + b.desc.Deprecated = true + if len(reason) > 0 { + b.desc.DeprecatedReason = strings.Join(reason, " ") + } + return b +} + +// Descriptor implements the ent.Field interface by returning its descriptor. +func (b *blobBuilder) Descriptor() *Descriptor { + return b.desc +} + // A Descriptor for field configuration. type Descriptor struct { Tag string // struct tag. @@ -1446,7 +1512,6 @@ type Descriptor struct { Comment string // field comment. Deprecated bool // mark the field as deprecated. DeprecatedReason string // deprecation reason. - Blob bool // field is stored in external blob storage. Err error } diff --git a/schema/field/field_test.go b/schema/field/field_test.go index 472a894f7..12ae1f1c0 100644 --- a/schema/field/field_test.go +++ b/schema/field/field_test.go @@ -943,7 +943,7 @@ func TestTypeString(t *testing.T) { assert.Equal(t, "bool", typ.String()) typ = field.TypeInvalid assert.Equal(t, "invalid", typ.String()) - typ = 21 + typ = 22 assert.Equal(t, "invalid", typ.String()) } @@ -959,7 +959,7 @@ func TestTypeValid(t *testing.T) { assert.True(t, typ.Valid()) typ = 0 assert.False(t, typ.Valid()) - typ = 21 + typ = 22 assert.False(t, typ.Valid()) } @@ -972,7 +972,7 @@ func TestTypeConstName(t *testing.T) { assert.Equal(t, "TypeInt64", typ.ConstName()) typ = field.TypeOther assert.Equal(t, "TypeOther", typ.ConstName()) - typ = 21 + typ = 22 assert.Equal(t, "invalid", typ.ConstName()) } diff --git a/schema/field/type.go b/schema/field/type.go index 92ab6975d..cdac06276 100644 --- a/schema/field/type.go +++ b/schema/field/type.go @@ -24,6 +24,7 @@ const ( TypeEnum TypeString TypeOther + TypeBlob TypeInt8 TypeInt16 TypeInt32 @@ -166,6 +167,7 @@ var ( TypeEnum: "string", TypeString: "string", TypeOther: "other", + TypeBlob: "io.Reader", TypeInt: "int", TypeInt8: "int8", TypeInt16: "int16", @@ -186,6 +188,7 @@ var ( TypeEnum: "TypeEnum", TypeBytes: "TypeBytes", TypeOther: "TypeOther", + TypeBlob: "TypeBlob", } )