Files
upstream-mirror 4b3ad0b611
Some checks failed
Atlas CI Public / lint (push) Failing after 13s
Atlas CI Public / sync (push) Has been skipped
snapshot of https://github.com/ent/ent.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 22:12:36 +05:00

156 lines
4.5 KiB
Go

// Copyright 2019-present Facebook Inc. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.
package sqlgraph
import (
"context"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
)
// BlobSpec configures SQL-level blob key queries and implements [ent.BlobQuerier].
type BlobSpec struct {
Driver dialect.Driver
Table string
Columns map[string]string // field name -> key column name
// CheckRefs holds the fields whose keys more than one row can hold, the ones
// declaring CheckRefs on the schema. Only these are looked up before a delete;
// a key generated fresh per write has no other holder to find. An empty
// CheckRefs makes [BlobSpec.ReferencedBlobKeys] a no-op.
CheckRefs map[string]bool
Predicate func(*sql.Selector)
}
// maxBlobKeysPerQuery bounds the keys per reference lookup, keeping the
// statement clear of driver placeholder limits on large deletes.
const maxBlobKeysPerQuery = 500
// ReferencedBlobKeys implements [ent.BlobRefChecker]. It returns the subset of keys
// some row holds in its field's key column. [BlobSpec.Predicate] is deliberately not
// applied: the question is which keys are in use anywhere, and callers time the lookup
// so the mutation's own rows already read the way they will settle.
//
// Only fields listed in [BlobSpec.CheckRefs] are looked up. Keys of any other field are
// reported as held by no row, which is what an unshared key strategy guarantees anyway.
//
// The lookup only asks whether a key is used, never how often, so an index on each
// blob key column lets it stop at the first match.
func (s *BlobSpec) ReferencedBlobKeys(ctx context.Context, keys []ent.BlobKey) ([]ent.BlobKey, error) {
if len(keys) == 0 || len(s.CheckRefs) == 0 {
return nil, nil
}
// Group the distinct keys by the column they would be stored in.
lookup := make(map[string][]any)
seen := make(map[ent.BlobKey]bool, len(keys))
for _, k := range keys {
if k.Key == "" || seen[k] || !s.CheckRefs[k.Field] {
continue
}
seen[k] = true
if col, ok := s.Columns[k.Field]; ok {
lookup[col] = append(lookup[col], k.Key)
}
}
var referenced []ent.BlobKey
for field := range s.CheckRefs {
col, ok := s.Columns[field]
if !ok {
continue
}
vals := lookup[col]
for len(vals) > 0 {
n := min(len(vals), maxBlobKeysPerQuery)
found, err := s.selectReferenced(ctx, col, vals[:n])
if err != nil {
return nil, err
}
for _, key := range found {
referenced = append(referenced, ent.BlobKey{Field: field, Key: key})
}
vals = vals[n:]
}
}
return referenced, nil
}
// selectReferenced returns which of vals appear in col.
func (s *BlobSpec) selectReferenced(ctx context.Context, col string, vals []any) ([]string, error) {
query, args := sql.Dialect(s.Driver.Dialect()).
Select(col).
Distinct().
From(sql.Table(s.Table)).
Where(sql.In(col, vals...)).
Query()
rows := &sql.Rows{}
if err := s.Driver.Query(ctx, query, args, rows); err != nil {
return nil, err
}
defer rows.Close()
var found []string
for rows.Next() {
var key string
if err := rows.Scan(&key); err != nil {
return nil, err
}
found = append(found, key)
}
return found, rows.Err()
}
// QueryBlobKeys implements [ent.BlobQuerier].
// If fields is nil, all columns are queried (for deletes);
// otherwise only the named fields are queried.
func (s *BlobSpec) QueryBlobKeys(ctx context.Context, fields []string) ([]ent.BlobKey, error) {
cols := s.Columns
if len(fields) > 0 {
cols = make(map[string]string, len(fields))
for _, f := range fields {
if c, ok := s.Columns[f]; ok {
cols[f] = c
}
}
}
if len(cols) == 0 {
return nil, nil
}
names := make([]string, 0, len(cols))
colNames := make([]string, 0, len(cols))
for field, col := range cols {
names = append(names, field)
colNames = append(colNames, col)
}
selector := sql.Dialect(s.Driver.Dialect()).
Select(colNames...).
From(sql.Table(s.Table))
if s.Predicate != nil {
s.Predicate(selector)
}
query, args := selector.Query()
rows := &sql.Rows{}
if err := s.Driver.Query(ctx, query, args, rows); err != nil {
return nil, err
}
defer rows.Close()
var keys []ent.BlobKey
for rows.Next() {
vals := make([]*string, len(colNames))
ptrs := make([]any, len(colNames))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err != nil {
return nil, err
}
for i, v := range vals {
if v != nil && *v != "" {
keys = append(keys, ent.BlobKey{Field: names[i], Key: *v})
}
}
}
return keys, rows.Err()
}