This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-ent-adapter.git
The following commit(s) were added to refs/heads/master by this push:
new ec45d2b feat: enforce the unique index on policy rules
ec45d2b is described below
commit ec45d2bec696b3b0d1a8c5111c78a55adec463da
Author: Yang Luo <[email protected]>
AuthorDate: Thu Aug 6 22:15:34 2026 +0800
feat: enforce the unique index on policy rules
---
README.md | 49 ++++
adapter.go | 206 ++++++++++++----
adapter_test.go | 69 ++++++
ent/casbinrule/casbinrule.go | 14 ++
ent/casbinrule_create.go | 546 +++++++++++++++++++++++++++++++++++++++++++
ent/casbinrule_update.go | 86 +++++++
ent/generate.go | 4 +-
ent/migrate/schema.go | 21 +-
ent/runtime.go | 14 ++
ent/schema/casbinrule.go | 37 ++-
go.sum | 10 +
11 files changed, 994 insertions(+), 62 deletions(-)
diff --git a/README.md b/README.md
index 90eb448..a2de5dd 100644
--- a/README.md
+++ b/README.md
@@ -125,6 +125,55 @@ func main() {
The database used in the adapter should be created manually before calling
`NewAdapter`. The adapter will automatically create the `casbin_rule` table if
it doesn't exist.
+## Upgrading an existing database
+
+A policy rule is now unique on `(ptype, v0, v1, v2, v3, v4, v5)`, and the seven
+columns are `varchar(100)` so that the index fits within the 3072-byte key
limit
+InnoDB enforces. Both are enforced by the automatic migration that runs inside
+`NewAdapter` and `NewAdapterWithClient`, so a database written by an earlier
+version needs a look before the first start on this version. A fresh database
+needs nothing.
+
+Check for rules that no longer fit. The adapter runs this check itself and
+refuses to migrate while it returns anything, because a MySQL server running
+without strict mode would truncate the values rather than reject them:
+
+```sql
+SELECT * FROM casbin_rules
+WHERE CHAR_LENGTH(ptype) > 100 OR CHAR_LENGTH(v0) > 100 OR CHAR_LENGTH(v1) >
100
+ OR CHAR_LENGTH(v2) > 100 OR CHAR_LENGTH(v3) > 100 OR CHAR_LENGTH(v4) > 100
+ OR CHAR_LENGTH(v5) > 100;
+```
+
+Check for rules stored more than once. Nothing enforced uniqueness before, so
+duplicates may have accumulated, and the index cannot be built while they
exist:
+
+```sql
+SELECT ptype, v0, v1, v2, v3, v4, v5, COUNT(*) AS copies
+FROM casbin_rules GROUP BY ptype, v0, v1, v2, v3, v4, v5 HAVING COUNT(*) > 1;
+```
+
+Duplicates are redundant by definition — casbin evaluates a rule the same way
+whether it is stored once or ten times — so keeping the lowest `id` of each
+group is safe:
+
+```sql
+DELETE c FROM casbin_rules c
+JOIN (
+ SELECT MIN(id) AS keep_id, ptype, v0, v1, v2, v3, v4, v5
+ FROM casbin_rules GROUP BY ptype, v0, v1, v2, v3, v4, v5 HAVING COUNT(*) > 1
+) d ON c.ptype = d.ptype AND c.v0 = d.v0 AND c.v1 = d.v1 AND c.v2 = d.v2
+ AND c.v3 = d.v3 AND c.v4 = d.v4 AND c.v5 = d.v5
+WHERE c.id > d.keep_id;
+```
+
+On PostgreSQL, use `DELETE FROM casbin_rules WHERE id NOT IN (SELECT MIN(id)
+FROM casbin_rules GROUP BY ptype, v0, v1, v2, v3, v4, v5);` instead.
+
+Adding a rule that is already stored stays a successful no-op rather than
+becoming an error, so replicas whose in-memory model has fallen behind another
+writer keep working.
+
## Getting Help
- [Casbin](https://github.com/apache/casbin)
diff --git a/adapter.go b/adapter.go
index 2d29291..054724a 100644
--- a/adapter.go
+++ b/adapter.go
@@ -29,6 +29,7 @@ import (
"github.com/casbin/casbin/v3/model"
"github.com/apache/casbin-ent-adapter/ent"
+ "github.com/apache/casbin-ent-adapter/ent/schema"
_ "github.com/go-sql-driver/mysql"
_ "github.com/jackc/pgx/v5/stdlib"
_ "github.com/lib/pq"
@@ -40,6 +41,21 @@ const (
DefaultDatabase = "casbin"
)
+// batchSize caps how many rows go into a single INSERT.
+const batchSize = 5000
+
+// policyColumns are the columns covered by the unique index on a policy rule.
+// They double as the conflict target of every insert.
+var policyColumns = []string{
+ casbinrule.FieldPtype,
+ casbinrule.FieldV0,
+ casbinrule.FieldV1,
+ casbinrule.FieldV2,
+ casbinrule.FieldV3,
+ casbinrule.FieldV4,
+ casbinrule.FieldV5,
+}
+
type Adapter struct {
client *ent.Client
ctx context.Context
@@ -88,7 +104,7 @@ func NewAdapter(driverName, dataSourceName string, options
...Option) (*Adapter,
return nil, err
}
}
- if err := client.Schema.Create(a.ctx); err != nil {
+ if err := a.migrate(); err != nil {
return nil, err
}
return a, nil
@@ -106,12 +122,69 @@ func NewAdapterWithClient(client *ent.Client, options
...Option) (*Adapter, erro
return nil, err
}
}
- if err := client.Schema.Create(a.ctx); err != nil {
+ if err := a.migrate(); err != nil {
return nil, err
}
return a, nil
}
+// migrate brings the schema up to date, guarding the two ways that can go
wrong
+// on a database created before the policy tuple became unique.
+func (a *Adapter) migrate() error {
+ if err := a.checkFieldLengths(); err != nil {
+ return err
+ }
+ if err := a.client.Schema.Create(a.ctx); err != nil {
+ return explainDuplicateRules(err)
+ }
+ return nil
+}
+
+// checkFieldLengths refuses to migrate a table still holding policy values
+// longer than the column width that the unique index requires. Narrowing such
a
+// column fails outright on a MySQL server in strict mode, but a server without
+// it truncates the values instead, quietly rewriting the stored policies.
+//
+// The probe is best effort. On a fresh database the table does not exist yet
and
+// the query fails, which says nothing about anyone's data, so probe errors are
+// dropped and the migration itself is left to report real problems.
+func (a *Adapter) checkFieldLengths() error {
+ tooLong, err := a.client.CasbinRule.Query().Where(func(s
*entsql.Selector) {
+ // LENGTH counts bytes on MySQL, so the portable spelling there
is
+ // CHAR_LENGTH, which SQLite in turn does not have.
+ length := "CHAR_LENGTH"
+ if s.Dialect() == dialect.SQLite {
+ length = "LENGTH"
+ }
+ overflows := make([]*entsql.Predicate, 0, len(policyColumns))
+ for _, column := range policyColumns {
+ overflows = append(overflows, entsql.ExprP(
+ fmt.Sprintf("%s(%s) > %d", length, s.C(column),
schema.MaxFieldLen)))
+ }
+ s.Where(entsql.Or(overflows...))
+ }).Exist(a.ctx)
+ if err != nil || !tooLong {
+ return nil
+ }
+ return fmt.Errorf("casbin_rules holds policy values longer than %d
characters, "+
+ "which the columns can no longer store; shorten them before
upgrading, "+
+ "otherwise the migration truncates them", schema.MaxFieldLen)
+}
+
+// explainDuplicateRules names the policy data behind a migration that failed
+// because the table already holds rows the new unique index forbids. Drivers
+// report it as a bare constraint violation naming only an index.
+func explainDuplicateRules(err error) error {
+ msg := strings.ToLower(err.Error())
+ for _, marker := range []string{"duplicate entry", "duplicate key
value", "unique constraint failed"} {
+ if strings.Contains(msg, marker) {
+ return errors.Wrap(err, "casbin_rules holds duplicate
policy rules, which the unique "+
+ "index on (ptype, v0..v5) forbids; delete the
redundant rows and retry")
+ }
+ }
+ return err
+}
+
// LoadPolicy loads all policy rules from the storage.
func (a *Adapter) LoadPolicy(model model.Model) error {
policies, err :=
a.client.CasbinRule.Query().Order(ent.Asc("id")).All(a.ctx)
@@ -184,36 +257,15 @@ func (a *Adapter) SavePolicy(model model.Model) error {
if _, err := tx.CasbinRule.Delete().Exec(a.ctx); err != nil {
return err
}
- lines := make([]*ent.CasbinRuleCreate, 0)
-
- for ptype, ast := range model["p"] {
- for _, policy := range ast.Policy {
- line := a.savePolicyLine(tx, ptype, policy)
- lines = append(lines, line)
- }
- }
+ lines := make([]policyLine, 0)
- for ptype, ast := range model["g"] {
- for _, policy := range ast.Policy {
- line := a.savePolicyLine(tx, ptype, policy)
- lines = append(lines, line)
+ for _, sec := range []string{"p", "g"} {
+ for ptype, ast := range model[sec] {
+ lines = append(lines, policyLines(ptype,
ast.Policy)...)
}
}
- // batch process
- batchSize := 5000
- for i := 0; i < len(lines); i += batchSize {
- end := i + batchSize
- if end > len(lines) {
- end = len(lines)
- }
- batch := lines[i:end]
-
- if _, err :=
tx.CasbinRule.CreateBulk(batch...).Save(a.ctx); err != nil {
- return err
- }
- }
- return nil
+ return a.insertPolicyLines(tx, lines)
})
}
@@ -221,8 +273,7 @@ func (a *Adapter) SavePolicy(model model.Model) error {
// This is part of the Auto-Save feature.
func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {
return a.WithTx(func(tx *ent.Tx) error {
- _, err := a.savePolicyLine(tx, ptype, rule).Save(a.ctx)
- return err
+ return a.insertPolicyLines(tx, []policyLine{{ptype: ptype,
rule: rule}})
})
}
@@ -402,6 +453,83 @@ func (a *Adapter) savePolicyLine(tx *ent.Tx, ptype string,
rule []string) *ent.C
return line
}
+// policyKey is the storage identity of a rule: the tuple covered by the unique
+// index. Two rules sharing a key map to the same row.
+type policyKey [7]string
+
+func newPolicyKey(ptype string, rule []string) policyKey {
+ key := policyKey{ptype}
+ for i := 0; i < len(rule) && i+1 < len(key); i++ {
+ key[i+1] = rule[i]
+ }
+ return key
+}
+
+// policyLine pairs a rule with its ptype so rules coming from different model
+// sections can be deduplicated against each other.
+type policyLine struct {
+ ptype string
+ rule []string
+}
+
+func policyLines(ptype string, rules [][]string) []policyLine {
+ lines := make([]policyLine, 0, len(rules))
+ for _, rule := range rules {
+ lines = append(lines, policyLine{ptype: ptype, rule: rule})
+ }
+ return lines
+}
+
+// dedupPolicyLines keeps the first line of every distinct key. A casbin model
+// can legitimately carry duplicates -- loading a policy file appends each line
+// without checking whether the model already holds it -- and writing them out
+// unchanged would now collide with the unique index.
+func dedupPolicyLines(lines []policyLine) []policyLine {
+ seen := make(map[policyKey]struct{}, len(lines))
+ out := make([]policyLine, 0, len(lines))
+ for _, line := range lines {
+ key := newPolicyKey(line.ptype, line.rule)
+ if _, ok := seen[key]; ok {
+ continue
+ }
+ seen[key] = struct{}{}
+ out = append(out, line)
+ }
+ return out
+}
+
+// insertPolicyLines writes lines in batches, leaving rows that already exist
+// untouched.
+//
+// Ignoring conflicts is what keeps writes idempotent now that the tuple is
+// unique. The enforcer screens duplicates against its in-memory model, but
that
+// model is per-process: a second replica, or one whose model has gone stale,
+// can still submit a rule that is already stored. That used to insert a second
+// identical row; it must not start failing the caller instead.
+func (a *Adapter) insertPolicyLines(tx *ent.Tx, lines []policyLine) error {
+ lines = dedupPolicyLines(lines)
+ for start := 0; start < len(lines); start += batchSize {
+ end := start + batchSize
+ if end > len(lines) {
+ end = len(lines)
+ }
+ batch := make([]*ent.CasbinRuleCreate, 0, end-start)
+ for _, line := range lines[start:end] {
+ batch = append(batch, a.savePolicyLine(tx, line.ptype,
line.rule))
+ }
+ // The conflict target is named explicitly because PostgreSQL
requires
+ // an inference specification for DO UPDATE. MySQL has no
target in its
+ // ON DUPLICATE KEY syntax and ignores it.
+ if err := tx.CasbinRule.CreateBulk(batch...).
+ OnConflictColumns(policyColumns...).
+ Ignore().
+ Exec(a.ctx); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
// UpdatePolicy updates a policy rule from storage.
// This is part of the Auto-Save feature.
func (a *Adapter) UpdatePolicy(sec string, ptype string, oldRule, newPolicy
[]string) error {
@@ -445,14 +573,7 @@ func (a *Adapter) UpdatePolicies(sec string, ptype string,
oldRules, newRules []
return err
}
}
- lines := make([]*ent.CasbinRuleCreate, 0)
- for _, policy := range newRules {
- lines = append(lines, a.savePolicyLine(tx, ptype,
policy))
- }
- if _, err := tx.CasbinRule.CreateBulk(lines...).Save(a.ctx);
err != nil {
- return err
- }
- return nil
+ return a.insertPolicyLines(tx, policyLines(ptype, newRules))
})
}
@@ -513,14 +634,7 @@ func (a *Adapter) UpdateFilteredPolicies(sec string, ptype
string, newPolicies [
}
func (a *Adapter) createPolicies(tx *ent.Tx, ptype string, policies
[][]string) error {
- lines := make([]*ent.CasbinRuleCreate, 0)
- for _, policy := range policies {
- lines = append(lines, a.savePolicyLine(tx, ptype, policy))
- }
- if _, err := tx.CasbinRule.CreateBulk(lines...).Save(a.ctx); err != nil
{
- return err
- }
- return nil
+ return a.insertPolicyLines(tx, policyLines(ptype, policies))
}
func CasbinRuleToStringArray(rule *ent.CasbinRule) []string {
diff --git a/adapter_test.go b/adapter_test.go
index 0a5fe67..21d58d5 100644
--- a/adapter_test.go
+++ b/adapter_test.go
@@ -23,6 +23,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/apache/casbin-ent-adapter/ent"
+ "github.com/apache/casbin-ent-adapter/ent/casbinrule"
)
func testGetPolicy(t *testing.T, e *casbin.Enforcer, res [][]string) {
@@ -263,6 +264,68 @@ func testFilteredPolicy(t *testing.T, a *Adapter) {
testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob",
"data2", "write"}})
}
+// testUniqueIndex covers the index on (ptype, v0..v5): storing a rule that is
+// already there is a no-op rather than a second row or an error.
+func testUniqueIndex(t *testing.T, a *Adapter) {
+ // initAdapter has just stored the four rules of rbac_policy.csv.
+ count, err := a.client.CasbinRule.Query().Count(a.ctx)
+ assert.Nil(t, err)
+ assert.Equal(t, 4, count)
+
+ // Re-adding a stored rule is what a replica does when its in-memory
model
+ // has not caught up with another writer.
+ assert.Nil(t, a.AddPolicy("p", "p", []string{"alice", "data1", "read"}))
+
+ // The same, in bulk, and with a duplicate inside the batch itself.
+ assert.Nil(t, a.AddPolicies("p", "p", [][]string{
+ {"alice", "data1", "read"},
+ {"eve", "data3", "read"},
+ {"eve", "data3", "read"},
+ }))
+
+ count, err = a.client.CasbinRule.Query().Count(a.ctx)
+ assert.Nil(t, err)
+ assert.Equal(t, 5, count)
+
+ eve, err :=
a.client.CasbinRule.Query().Where(casbinrule.V0EQ("eve")).Count(a.ctx)
+ assert.Nil(t, err)
+ assert.Equal(t, 1, eve)
+
+ // A model carrying the same rule twice must still save.
+ e, err := casbin.NewEnforcer("examples/rbac_model.conf")
+ assert.Nil(t, err)
+ m := e.GetModel()
+ assert.Nil(t, m.AddPolicy("p", "p", []string{"carol", "data4", "read"}))
+ assert.Nil(t, m.AddPolicy("p", "p", []string{"carol", "data4", "read"}))
+ assert.Nil(t, a.SavePolicy(m))
+
+ count, err = a.client.CasbinRule.Query().Count(a.ctx)
+ assert.Nil(t, err)
+ assert.Equal(t, 1, count)
+}
+
+func TestDedupPolicyLines(t *testing.T) {
+ lines := []policyLine{
+ {ptype: "p", rule: []string{"alice", "data1", "read"}},
+ {ptype: "p", rule: []string{"alice", "data1", "read"}},
+ {ptype: "p", rule: []string{"alice", "data1", "write"}},
+ {ptype: "g", rule: []string{"alice", "data1", "read"}},
+ // Trailing empty values are what the unused columns already
default
+ // to, so this is the same row as the shorter rule above.
+ {ptype: "p", rule: []string{"alice", "data1", "write", ""}},
+ }
+
+ assert.Equal(t, []policyLine{lines[0], lines[2], lines[3]},
dedupPolicyLines(lines))
+}
+
+func TestNewPolicyKeyIgnoresExtraValues(t *testing.T) {
+ // Only Ptype and V0..V5 are stored, so a longer rule cannot overflow
the
+ // key, and the values past V5 do not take part in its identity.
+ assert.Equal(t,
+ newPolicyKey("p", []string{"0", "1", "2", "3", "4", "5"}),
+ newPolicyKey("p", []string{"0", "1", "2", "3", "4", "5", "6"}))
+}
+
func TestAdapters(t *testing.T) {
a := initAdapter(t, "mysql", "root:@tcp(127.0.0.1:3306)/casbin")
testAutoSave(t, a)
@@ -299,4 +362,10 @@ func TestAdapters(t *testing.T) {
testUpdatePolicies(t, a)
testUpdateFilteredPolicies(t, a)
testFilteredPolicy(t, a)
+
+ a = initAdapter(t, "mysql", "root:@tcp(127.0.0.1:3306)/casbin")
+ testUniqueIndex(t, a)
+
+ a = initAdapter(t, "postgres", "user=postgres password=postgres
host=127.0.0.1 port=5432 sslmode=disable dbname=casbin")
+ testUniqueIndex(t, a)
}
diff --git a/ent/casbinrule/casbinrule.go b/ent/casbinrule/casbinrule.go
index d739603..a86f5cc 100644
--- a/ent/casbinrule/casbinrule.go
+++ b/ent/casbinrule/casbinrule.go
@@ -54,18 +54,32 @@ func ValidColumn(column string) bool {
var (
// DefaultPtype holds the default value on creation for the "Ptype"
field.
DefaultPtype string
+ // PtypeValidator is a validator for the "Ptype" field. It is called by
the builders before save.
+ PtypeValidator func(string) error
// DefaultV0 holds the default value on creation for the "V0" field.
DefaultV0 string
+ // V0Validator is a validator for the "V0" field. It is called by the
builders before save.
+ V0Validator func(string) error
// DefaultV1 holds the default value on creation for the "V1" field.
DefaultV1 string
+ // V1Validator is a validator for the "V1" field. It is called by the
builders before save.
+ V1Validator func(string) error
// DefaultV2 holds the default value on creation for the "V2" field.
DefaultV2 string
+ // V2Validator is a validator for the "V2" field. It is called by the
builders before save.
+ V2Validator func(string) error
// DefaultV3 holds the default value on creation for the "V3" field.
DefaultV3 string
+ // V3Validator is a validator for the "V3" field. It is called by the
builders before save.
+ V3Validator func(string) error
// DefaultV4 holds the default value on creation for the "V4" field.
DefaultV4 string
+ // V4Validator is a validator for the "V4" field. It is called by the
builders before save.
+ V4Validator func(string) error
// DefaultV5 holds the default value on creation for the "V5" field.
DefaultV5 string
+ // V5Validator is a validator for the "V5" field. It is called by the
builders before save.
+ V5Validator func(string) error
)
// OrderOption defines the ordering options for the CasbinRule queries.
diff --git a/ent/casbinrule_create.go b/ent/casbinrule_create.go
index b906adb..0ebdbff 100644
--- a/ent/casbinrule_create.go
+++ b/ent/casbinrule_create.go
@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
+ "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/apache/casbin-ent-adapter/ent/casbinrule"
@@ -17,6 +18,7 @@ type CasbinRuleCreate struct {
config
mutation *CasbinRuleMutation
hooks []Hook
+ conflict []sql.ConflictOption
}
// SetPtype sets the "Ptype" field.
@@ -187,24 +189,59 @@ func (_c *CasbinRuleCreate) check() error {
if _, ok := _c.mutation.Ptype(); !ok {
return &ValidationError{Name: "Ptype", err: errors.New(`ent:
missing required field "CasbinRule.Ptype"`)}
}
+ if v, ok := _c.mutation.Ptype(); ok {
+ if err := casbinrule.PtypeValidator(v); err != nil {
+ return &ValidationError{Name: "Ptype", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.Ptype": %w`, err)}
+ }
+ }
if _, ok := _c.mutation.V0(); !ok {
return &ValidationError{Name: "V0", err: errors.New(`ent:
missing required field "CasbinRule.V0"`)}
}
+ if v, ok := _c.mutation.V0(); ok {
+ if err := casbinrule.V0Validator(v); err != nil {
+ return &ValidationError{Name: "V0", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V0": %w`, err)}
+ }
+ }
if _, ok := _c.mutation.V1(); !ok {
return &ValidationError{Name: "V1", err: errors.New(`ent:
missing required field "CasbinRule.V1"`)}
}
+ if v, ok := _c.mutation.V1(); ok {
+ if err := casbinrule.V1Validator(v); err != nil {
+ return &ValidationError{Name: "V1", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V1": %w`, err)}
+ }
+ }
if _, ok := _c.mutation.V2(); !ok {
return &ValidationError{Name: "V2", err: errors.New(`ent:
missing required field "CasbinRule.V2"`)}
}
+ if v, ok := _c.mutation.V2(); ok {
+ if err := casbinrule.V2Validator(v); err != nil {
+ return &ValidationError{Name: "V2", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V2": %w`, err)}
+ }
+ }
if _, ok := _c.mutation.V3(); !ok {
return &ValidationError{Name: "V3", err: errors.New(`ent:
missing required field "CasbinRule.V3"`)}
}
+ if v, ok := _c.mutation.V3(); ok {
+ if err := casbinrule.V3Validator(v); err != nil {
+ return &ValidationError{Name: "V3", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V3": %w`, err)}
+ }
+ }
if _, ok := _c.mutation.V4(); !ok {
return &ValidationError{Name: "V4", err: errors.New(`ent:
missing required field "CasbinRule.V4"`)}
}
+ if v, ok := _c.mutation.V4(); ok {
+ if err := casbinrule.V4Validator(v); err != nil {
+ return &ValidationError{Name: "V4", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V4": %w`, err)}
+ }
+ }
if _, ok := _c.mutation.V5(); !ok {
return &ValidationError{Name: "V5", err: errors.New(`ent:
missing required field "CasbinRule.V5"`)}
}
+ if v, ok := _c.mutation.V5(); ok {
+ if err := casbinrule.V5Validator(v); err != nil {
+ return &ValidationError{Name: "V5", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V5": %w`, err)}
+ }
+ }
return nil
}
@@ -231,6 +268,7 @@ func (_c *CasbinRuleCreate) createSpec() (*CasbinRule,
*sqlgraph.CreateSpec) {
_node = &CasbinRule{config: _c.config}
_spec = sqlgraph.NewCreateSpec(casbinrule.Table,
sqlgraph.NewFieldSpec(casbinrule.FieldID, field.TypeInt))
)
+ _spec.OnConflict = _c.conflict
if value, ok := _c.mutation.Ptype(); ok {
_spec.SetField(casbinrule.FieldPtype, field.TypeString, value)
_node.Ptype = value
@@ -262,11 +300,316 @@ func (_c *CasbinRuleCreate) createSpec() (*CasbinRule,
*sqlgraph.CreateSpec) {
return _node, _spec
}
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+// client.CasbinRule.Create().
+// SetPtype(v).
+// OnConflict(
+// // Update the row with the new values
+// // the was proposed for insertion.
+// sql.ResolveWithNewValues(),
+// ).
+// // Override some of the fields with custom
+// // update values.
+// Update(func(u *ent.CasbinRuleUpsert) {
+// SetPtype(v+v).
+// }).
+// Exec(ctx)
+func (_c *CasbinRuleCreate) OnConflict(opts ...sql.ConflictOption)
*CasbinRuleUpsertOne {
+ _c.conflict = opts
+ return &CasbinRuleUpsertOne{
+ create: _c,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.CasbinRule.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (_c *CasbinRuleCreate) OnConflictColumns(columns ...string)
*CasbinRuleUpsertOne {
+ _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
+ return &CasbinRuleUpsertOne{
+ create: _c,
+ }
+}
+
+type (
+ // CasbinRuleUpsertOne is the builder for "upsert"-ing
+ // one CasbinRule node.
+ CasbinRuleUpsertOne struct {
+ create *CasbinRuleCreate
+ }
+
+ // CasbinRuleUpsert is the "OnConflict" setter.
+ CasbinRuleUpsert struct {
+ *sql.UpdateSet
+ }
+)
+
+// SetPtype sets the "Ptype" field.
+func (u *CasbinRuleUpsert) SetPtype(v string) *CasbinRuleUpsert {
+ u.Set(casbinrule.FieldPtype, v)
+ return u
+}
+
+// UpdatePtype sets the "Ptype" field to the value that was provided on create.
+func (u *CasbinRuleUpsert) UpdatePtype() *CasbinRuleUpsert {
+ u.SetExcluded(casbinrule.FieldPtype)
+ return u
+}
+
+// SetV0 sets the "V0" field.
+func (u *CasbinRuleUpsert) SetV0(v string) *CasbinRuleUpsert {
+ u.Set(casbinrule.FieldV0, v)
+ return u
+}
+
+// UpdateV0 sets the "V0" field to the value that was provided on create.
+func (u *CasbinRuleUpsert) UpdateV0() *CasbinRuleUpsert {
+ u.SetExcluded(casbinrule.FieldV0)
+ return u
+}
+
+// SetV1 sets the "V1" field.
+func (u *CasbinRuleUpsert) SetV1(v string) *CasbinRuleUpsert {
+ u.Set(casbinrule.FieldV1, v)
+ return u
+}
+
+// UpdateV1 sets the "V1" field to the value that was provided on create.
+func (u *CasbinRuleUpsert) UpdateV1() *CasbinRuleUpsert {
+ u.SetExcluded(casbinrule.FieldV1)
+ return u
+}
+
+// SetV2 sets the "V2" field.
+func (u *CasbinRuleUpsert) SetV2(v string) *CasbinRuleUpsert {
+ u.Set(casbinrule.FieldV2, v)
+ return u
+}
+
+// UpdateV2 sets the "V2" field to the value that was provided on create.
+func (u *CasbinRuleUpsert) UpdateV2() *CasbinRuleUpsert {
+ u.SetExcluded(casbinrule.FieldV2)
+ return u
+}
+
+// SetV3 sets the "V3" field.
+func (u *CasbinRuleUpsert) SetV3(v string) *CasbinRuleUpsert {
+ u.Set(casbinrule.FieldV3, v)
+ return u
+}
+
+// UpdateV3 sets the "V3" field to the value that was provided on create.
+func (u *CasbinRuleUpsert) UpdateV3() *CasbinRuleUpsert {
+ u.SetExcluded(casbinrule.FieldV3)
+ return u
+}
+
+// SetV4 sets the "V4" field.
+func (u *CasbinRuleUpsert) SetV4(v string) *CasbinRuleUpsert {
+ u.Set(casbinrule.FieldV4, v)
+ return u
+}
+
+// UpdateV4 sets the "V4" field to the value that was provided on create.
+func (u *CasbinRuleUpsert) UpdateV4() *CasbinRuleUpsert {
+ u.SetExcluded(casbinrule.FieldV4)
+ return u
+}
+
+// SetV5 sets the "V5" field.
+func (u *CasbinRuleUpsert) SetV5(v string) *CasbinRuleUpsert {
+ u.Set(casbinrule.FieldV5, v)
+ return u
+}
+
+// UpdateV5 sets the "V5" field to the value that was provided on create.
+func (u *CasbinRuleUpsert) UpdateV5() *CasbinRuleUpsert {
+ u.SetExcluded(casbinrule.FieldV5)
+ return u
+}
+
+// UpdateNewValues updates the mutable fields using the new values that were
set on create.
+// Using this option is equivalent to using:
+//
+// client.CasbinRule.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *CasbinRuleUpsertOne) UpdateNewValues() *CasbinRuleUpsertOne {
+ u.create.conflict = append(u.create.conflict,
sql.ResolveWithNewValues())
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.CasbinRule.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *CasbinRuleUpsertOne) Ignore() *CasbinRuleUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
+ return u
+}
+
+// DoNothing configures the conflict_action to `DO NOTHING`.
+// Supported only by SQLite and PostgreSQL.
+func (u *CasbinRuleUpsertOne) DoNothing() *CasbinRuleUpsertOne {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the
CasbinRuleCreate.OnConflict
+// documentation for more info.
+func (u *CasbinRuleUpsertOne) Update(set func(*CasbinRuleUpsert))
*CasbinRuleUpsertOne {
+ u.create.conflict = append(u.create.conflict,
sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&CasbinRuleUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetPtype sets the "Ptype" field.
+func (u *CasbinRuleUpsertOne) SetPtype(v string) *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetPtype(v)
+ })
+}
+
+// UpdatePtype sets the "Ptype" field to the value that was provided on create.
+func (u *CasbinRuleUpsertOne) UpdatePtype() *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdatePtype()
+ })
+}
+
+// SetV0 sets the "V0" field.
+func (u *CasbinRuleUpsertOne) SetV0(v string) *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetV0(v)
+ })
+}
+
+// UpdateV0 sets the "V0" field to the value that was provided on create.
+func (u *CasbinRuleUpsertOne) UpdateV0() *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdateV0()
+ })
+}
+
+// SetV1 sets the "V1" field.
+func (u *CasbinRuleUpsertOne) SetV1(v string) *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetV1(v)
+ })
+}
+
+// UpdateV1 sets the "V1" field to the value that was provided on create.
+func (u *CasbinRuleUpsertOne) UpdateV1() *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdateV1()
+ })
+}
+
+// SetV2 sets the "V2" field.
+func (u *CasbinRuleUpsertOne) SetV2(v string) *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetV2(v)
+ })
+}
+
+// UpdateV2 sets the "V2" field to the value that was provided on create.
+func (u *CasbinRuleUpsertOne) UpdateV2() *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdateV2()
+ })
+}
+
+// SetV3 sets the "V3" field.
+func (u *CasbinRuleUpsertOne) SetV3(v string) *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetV3(v)
+ })
+}
+
+// UpdateV3 sets the "V3" field to the value that was provided on create.
+func (u *CasbinRuleUpsertOne) UpdateV3() *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdateV3()
+ })
+}
+
+// SetV4 sets the "V4" field.
+func (u *CasbinRuleUpsertOne) SetV4(v string) *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetV4(v)
+ })
+}
+
+// UpdateV4 sets the "V4" field to the value that was provided on create.
+func (u *CasbinRuleUpsertOne) UpdateV4() *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdateV4()
+ })
+}
+
+// SetV5 sets the "V5" field.
+func (u *CasbinRuleUpsertOne) SetV5(v string) *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetV5(v)
+ })
+}
+
+// UpdateV5 sets the "V5" field to the value that was provided on create.
+func (u *CasbinRuleUpsertOne) UpdateV5() *CasbinRuleUpsertOne {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdateV5()
+ })
+}
+
+// Exec executes the query.
+func (u *CasbinRuleUpsertOne) Exec(ctx context.Context) error {
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for
CasbinRuleCreate.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *CasbinRuleUpsertOne) ExecX(ctx context.Context) {
+ if err := u.create.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
+
+// Exec executes the UPSERT query and returns the inserted/updated ID.
+func (u *CasbinRuleUpsertOne) ID(ctx context.Context) (id int, err error) {
+ node, err := u.create.Save(ctx)
+ if err != nil {
+ return id, err
+ }
+ return node.ID, nil
+}
+
+// IDX is like ID, but panics if an error occurs.
+func (u *CasbinRuleUpsertOne) IDX(ctx context.Context) int {
+ id, err := u.ID(ctx)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
// CasbinRuleCreateBulk is the builder for creating many CasbinRule entities
in bulk.
type CasbinRuleCreateBulk struct {
config
err error
builders []*CasbinRuleCreate
+ conflict []sql.ConflictOption
}
// Save creates the CasbinRule entities in the database.
@@ -296,6 +639,7 @@ func (_c *CasbinRuleCreateBulk) Save(ctx context.Context)
([]*CasbinRule, error)
_, err = mutators[i+1].Mutate(root,
_c.builders[i+1].mutation)
} else {
spec :=
&sqlgraph.BatchCreateSpec{Nodes: specs}
+ spec.OnConflict = _c.conflict
// Invoke the actual operation on the
latest mutation in the chain.
if err = sqlgraph.BatchCreate(ctx,
_c.driver, spec); err != nil {
if
sqlgraph.IsConstraintError(err) {
@@ -349,3 +693,205 @@ func (_c *CasbinRuleCreateBulk) ExecX(ctx
context.Context) {
panic(err)
}
}
+
+// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
+// of the `INSERT` statement. For example:
+//
+// client.CasbinRule.CreateBulk(builders...).
+// OnConflict(
+// // Update the row with the new values
+// // the was proposed for insertion.
+// sql.ResolveWithNewValues(),
+// ).
+// // Override some of the fields with custom
+// // update values.
+// Update(func(u *ent.CasbinRuleUpsert) {
+// SetPtype(v+v).
+// }).
+// Exec(ctx)
+func (_c *CasbinRuleCreateBulk) OnConflict(opts ...sql.ConflictOption)
*CasbinRuleUpsertBulk {
+ _c.conflict = opts
+ return &CasbinRuleUpsertBulk{
+ create: _c,
+ }
+}
+
+// OnConflictColumns calls `OnConflict` and configures the columns
+// as conflict target. Using this option is equivalent to using:
+//
+// client.CasbinRule.Create().
+// OnConflict(sql.ConflictColumns(columns...)).
+// Exec(ctx)
+func (_c *CasbinRuleCreateBulk) OnConflictColumns(columns ...string)
*CasbinRuleUpsertBulk {
+ _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
+ return &CasbinRuleUpsertBulk{
+ create: _c,
+ }
+}
+
+// CasbinRuleUpsertBulk is the builder for "upsert"-ing
+// a bulk of CasbinRule nodes.
+type CasbinRuleUpsertBulk struct {
+ create *CasbinRuleCreateBulk
+}
+
+// UpdateNewValues updates the mutable fields using the new values that
+// were set on create. Using this option is equivalent to using:
+//
+// client.CasbinRule.Create().
+// OnConflict(
+// sql.ResolveWithNewValues(),
+// ).
+// Exec(ctx)
+func (u *CasbinRuleUpsertBulk) UpdateNewValues() *CasbinRuleUpsertBulk {
+ u.create.conflict = append(u.create.conflict,
sql.ResolveWithNewValues())
+ return u
+}
+
+// Ignore sets each column to itself in case of conflict.
+// Using this option is equivalent to using:
+//
+// client.CasbinRule.Create().
+// OnConflict(sql.ResolveWithIgnore()).
+// Exec(ctx)
+func (u *CasbinRuleUpsertBulk) Ignore() *CasbinRuleUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
+ return u
+}
+
+// DoNothing configures the conflict_action to `DO NOTHING`.
+// Supported only by SQLite and PostgreSQL.
+func (u *CasbinRuleUpsertBulk) DoNothing() *CasbinRuleUpsertBulk {
+ u.create.conflict = append(u.create.conflict, sql.DoNothing())
+ return u
+}
+
+// Update allows overriding fields `UPDATE` values. See the
CasbinRuleCreateBulk.OnConflict
+// documentation for more info.
+func (u *CasbinRuleUpsertBulk) Update(set func(*CasbinRuleUpsert))
*CasbinRuleUpsertBulk {
+ u.create.conflict = append(u.create.conflict,
sql.ResolveWith(func(update *sql.UpdateSet) {
+ set(&CasbinRuleUpsert{UpdateSet: update})
+ }))
+ return u
+}
+
+// SetPtype sets the "Ptype" field.
+func (u *CasbinRuleUpsertBulk) SetPtype(v string) *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetPtype(v)
+ })
+}
+
+// UpdatePtype sets the "Ptype" field to the value that was provided on create.
+func (u *CasbinRuleUpsertBulk) UpdatePtype() *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdatePtype()
+ })
+}
+
+// SetV0 sets the "V0" field.
+func (u *CasbinRuleUpsertBulk) SetV0(v string) *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetV0(v)
+ })
+}
+
+// UpdateV0 sets the "V0" field to the value that was provided on create.
+func (u *CasbinRuleUpsertBulk) UpdateV0() *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdateV0()
+ })
+}
+
+// SetV1 sets the "V1" field.
+func (u *CasbinRuleUpsertBulk) SetV1(v string) *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetV1(v)
+ })
+}
+
+// UpdateV1 sets the "V1" field to the value that was provided on create.
+func (u *CasbinRuleUpsertBulk) UpdateV1() *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdateV1()
+ })
+}
+
+// SetV2 sets the "V2" field.
+func (u *CasbinRuleUpsertBulk) SetV2(v string) *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetV2(v)
+ })
+}
+
+// UpdateV2 sets the "V2" field to the value that was provided on create.
+func (u *CasbinRuleUpsertBulk) UpdateV2() *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdateV2()
+ })
+}
+
+// SetV3 sets the "V3" field.
+func (u *CasbinRuleUpsertBulk) SetV3(v string) *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetV3(v)
+ })
+}
+
+// UpdateV3 sets the "V3" field to the value that was provided on create.
+func (u *CasbinRuleUpsertBulk) UpdateV3() *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdateV3()
+ })
+}
+
+// SetV4 sets the "V4" field.
+func (u *CasbinRuleUpsertBulk) SetV4(v string) *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetV4(v)
+ })
+}
+
+// UpdateV4 sets the "V4" field to the value that was provided on create.
+func (u *CasbinRuleUpsertBulk) UpdateV4() *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdateV4()
+ })
+}
+
+// SetV5 sets the "V5" field.
+func (u *CasbinRuleUpsertBulk) SetV5(v string) *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.SetV5(v)
+ })
+}
+
+// UpdateV5 sets the "V5" field to the value that was provided on create.
+func (u *CasbinRuleUpsertBulk) UpdateV5() *CasbinRuleUpsertBulk {
+ return u.Update(func(s *CasbinRuleUpsert) {
+ s.UpdateV5()
+ })
+}
+
+// Exec executes the query.
+func (u *CasbinRuleUpsertBulk) Exec(ctx context.Context) error {
+ if u.create.err != nil {
+ return u.create.err
+ }
+ for i, b := range u.create.builders {
+ if len(b.conflict) != 0 {
+ return fmt.Errorf("ent: OnConflict was set for builder
%d. Set it on the CasbinRuleCreateBulk instead", i)
+ }
+ }
+ if len(u.create.conflict) == 0 {
+ return errors.New("ent: missing options for
CasbinRuleCreateBulk.OnConflict")
+ }
+ return u.create.Exec(ctx)
+}
+
+// ExecX is like Exec, but panics if an error occurs.
+func (u *CasbinRuleUpsertBulk) ExecX(ctx context.Context) {
+ if err := u.create.Exec(ctx); err != nil {
+ panic(err)
+ }
+}
diff --git a/ent/casbinrule_update.go b/ent/casbinrule_update.go
index e54e7a3..eea3ef6 100644
--- a/ent/casbinrule_update.go
+++ b/ent/casbinrule_update.go
@@ -157,7 +157,50 @@ func (_u *CasbinRuleUpdate) ExecX(ctx context.Context) {
}
}
+// check runs all checks and user-defined validators on the builder.
+func (_u *CasbinRuleUpdate) check() error {
+ if v, ok := _u.mutation.Ptype(); ok {
+ if err := casbinrule.PtypeValidator(v); err != nil {
+ return &ValidationError{Name: "Ptype", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.Ptype": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.V0(); ok {
+ if err := casbinrule.V0Validator(v); err != nil {
+ return &ValidationError{Name: "V0", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V0": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.V1(); ok {
+ if err := casbinrule.V1Validator(v); err != nil {
+ return &ValidationError{Name: "V1", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V1": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.V2(); ok {
+ if err := casbinrule.V2Validator(v); err != nil {
+ return &ValidationError{Name: "V2", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V2": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.V3(); ok {
+ if err := casbinrule.V3Validator(v); err != nil {
+ return &ValidationError{Name: "V3", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V3": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.V4(); ok {
+ if err := casbinrule.V4Validator(v); err != nil {
+ return &ValidationError{Name: "V4", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V4": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.V5(); ok {
+ if err := casbinrule.V5Validator(v); err != nil {
+ return &ValidationError{Name: "V5", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V5": %w`, err)}
+ }
+ }
+ return nil
+}
+
func (_u *CasbinRuleUpdate) sqlSave(ctx context.Context) (_node int, err
error) {
+ if err := _u.check(); err != nil {
+ return _node, err
+ }
_spec := sqlgraph.NewUpdateSpec(casbinrule.Table, casbinrule.Columns,
sqlgraph.NewFieldSpec(casbinrule.FieldID, field.TypeInt))
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
@@ -350,7 +393,50 @@ func (_u *CasbinRuleUpdateOne) ExecX(ctx context.Context) {
}
}
+// check runs all checks and user-defined validators on the builder.
+func (_u *CasbinRuleUpdateOne) check() error {
+ if v, ok := _u.mutation.Ptype(); ok {
+ if err := casbinrule.PtypeValidator(v); err != nil {
+ return &ValidationError{Name: "Ptype", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.Ptype": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.V0(); ok {
+ if err := casbinrule.V0Validator(v); err != nil {
+ return &ValidationError{Name: "V0", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V0": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.V1(); ok {
+ if err := casbinrule.V1Validator(v); err != nil {
+ return &ValidationError{Name: "V1", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V1": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.V2(); ok {
+ if err := casbinrule.V2Validator(v); err != nil {
+ return &ValidationError{Name: "V2", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V2": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.V3(); ok {
+ if err := casbinrule.V3Validator(v); err != nil {
+ return &ValidationError{Name: "V3", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V3": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.V4(); ok {
+ if err := casbinrule.V4Validator(v); err != nil {
+ return &ValidationError{Name: "V4", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V4": %w`, err)}
+ }
+ }
+ if v, ok := _u.mutation.V5(); ok {
+ if err := casbinrule.V5Validator(v); err != nil {
+ return &ValidationError{Name: "V5", err:
fmt.Errorf(`ent: validator failed for field "CasbinRule.V5": %w`, err)}
+ }
+ }
+ return nil
+}
+
func (_u *CasbinRuleUpdateOne) sqlSave(ctx context.Context) (_node
*CasbinRule, err error) {
+ if err := _u.check(); err != nil {
+ return _node, err
+ }
_spec := sqlgraph.NewUpdateSpec(casbinrule.Table, casbinrule.Columns,
sqlgraph.NewFieldSpec(casbinrule.FieldID, field.TypeInt))
id, ok := _u.mutation.ID()
if !ok {
diff --git a/ent/generate.go b/ent/generate.go
index 8d3fdfd..6affc35 100644
--- a/ent/generate.go
+++ b/ent/generate.go
@@ -1,3 +1,5 @@
package ent
-//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate ./schema
+// The sql/upsert feature generates the OnConflict builders the adapter uses to
+// make policy inserts idempotent against the unique index on (ptype, v0..v5).
+//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature
sql/upsert ./schema
diff --git a/ent/migrate/schema.go b/ent/migrate/schema.go
index 4e3cd0e..0e980df 100644
--- a/ent/migrate/schema.go
+++ b/ent/migrate/schema.go
@@ -11,19 +11,26 @@ var (
// CasbinRulesColumns holds the columns for the "casbin_rules" table.
CasbinRulesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt, Increment: true},
- {Name: "ptype", Type: field.TypeString, Default: ""},
- {Name: "v0", Type: field.TypeString, Default: ""},
- {Name: "v1", Type: field.TypeString, Default: ""},
- {Name: "v2", Type: field.TypeString, Default: ""},
- {Name: "v3", Type: field.TypeString, Default: ""},
- {Name: "v4", Type: field.TypeString, Default: ""},
- {Name: "v5", Type: field.TypeString, Default: ""},
+ {Name: "ptype", Type: field.TypeString, Size: 100, Default: ""},
+ {Name: "v0", Type: field.TypeString, Size: 100, Default: ""},
+ {Name: "v1", Type: field.TypeString, Size: 100, Default: ""},
+ {Name: "v2", Type: field.TypeString, Size: 100, Default: ""},
+ {Name: "v3", Type: field.TypeString, Size: 100, Default: ""},
+ {Name: "v4", Type: field.TypeString, Size: 100, Default: ""},
+ {Name: "v5", Type: field.TypeString, Size: 100, Default: ""},
}
// CasbinRulesTable holds the schema information for the "casbin_rules"
table.
CasbinRulesTable = &schema.Table{
Name: "casbin_rules",
Columns: CasbinRulesColumns,
PrimaryKey: []*schema.Column{CasbinRulesColumns[0]},
+ Indexes: []*schema.Index{
+ {
+ Name: "casbinrule_ptype_v0_v1_v2_v3_v4_v5",
+ Unique: true,
+ Columns:
[]*schema.Column{CasbinRulesColumns[1], CasbinRulesColumns[2],
CasbinRulesColumns[3], CasbinRulesColumns[4], CasbinRulesColumns[5],
CasbinRulesColumns[6], CasbinRulesColumns[7]},
+ },
+ },
}
// Tables holds all the tables in the schema.
Tables = []*schema.Table{
diff --git a/ent/runtime.go b/ent/runtime.go
index d893770..0be8060 100644
--- a/ent/runtime.go
+++ b/ent/runtime.go
@@ -17,28 +17,42 @@ func init() {
casbinruleDescPtype := casbinruleFields[0].Descriptor()
// casbinrule.DefaultPtype holds the default value on creation for the
Ptype field.
casbinrule.DefaultPtype = casbinruleDescPtype.Default.(string)
+ // casbinrule.PtypeValidator is a validator for the "Ptype" field. It
is called by the builders before save.
+ casbinrule.PtypeValidator =
casbinruleDescPtype.Validators[0].(func(string) error)
// casbinruleDescV0 is the schema descriptor for V0 field.
casbinruleDescV0 := casbinruleFields[1].Descriptor()
// casbinrule.DefaultV0 holds the default value on creation for the V0
field.
casbinrule.DefaultV0 = casbinruleDescV0.Default.(string)
+ // casbinrule.V0Validator is a validator for the "V0" field. It is
called by the builders before save.
+ casbinrule.V0Validator = casbinruleDescV0.Validators[0].(func(string)
error)
// casbinruleDescV1 is the schema descriptor for V1 field.
casbinruleDescV1 := casbinruleFields[2].Descriptor()
// casbinrule.DefaultV1 holds the default value on creation for the V1
field.
casbinrule.DefaultV1 = casbinruleDescV1.Default.(string)
+ // casbinrule.V1Validator is a validator for the "V1" field. It is
called by the builders before save.
+ casbinrule.V1Validator = casbinruleDescV1.Validators[0].(func(string)
error)
// casbinruleDescV2 is the schema descriptor for V2 field.
casbinruleDescV2 := casbinruleFields[3].Descriptor()
// casbinrule.DefaultV2 holds the default value on creation for the V2
field.
casbinrule.DefaultV2 = casbinruleDescV2.Default.(string)
+ // casbinrule.V2Validator is a validator for the "V2" field. It is
called by the builders before save.
+ casbinrule.V2Validator = casbinruleDescV2.Validators[0].(func(string)
error)
// casbinruleDescV3 is the schema descriptor for V3 field.
casbinruleDescV3 := casbinruleFields[4].Descriptor()
// casbinrule.DefaultV3 holds the default value on creation for the V3
field.
casbinrule.DefaultV3 = casbinruleDescV3.Default.(string)
+ // casbinrule.V3Validator is a validator for the "V3" field. It is
called by the builders before save.
+ casbinrule.V3Validator = casbinruleDescV3.Validators[0].(func(string)
error)
// casbinruleDescV4 is the schema descriptor for V4 field.
casbinruleDescV4 := casbinruleFields[5].Descriptor()
// casbinrule.DefaultV4 holds the default value on creation for the V4
field.
casbinrule.DefaultV4 = casbinruleDescV4.Default.(string)
+ // casbinrule.V4Validator is a validator for the "V4" field. It is
called by the builders before save.
+ casbinrule.V4Validator = casbinruleDescV4.Validators[0].(func(string)
error)
// casbinruleDescV5 is the schema descriptor for V5 field.
casbinruleDescV5 := casbinruleFields[6].Descriptor()
// casbinrule.DefaultV5 holds the default value on creation for the V5
field.
casbinrule.DefaultV5 = casbinruleDescV5.Default.(string)
+ // casbinrule.V5Validator is a validator for the "V5" field. It is
called by the builders before save.
+ casbinrule.V5Validator = casbinruleDescV5.Validators[0].(func(string)
error)
}
diff --git a/ent/schema/casbinrule.go b/ent/schema/casbinrule.go
index 4c87380..497c2ef 100644
--- a/ent/schema/casbinrule.go
+++ b/ent/schema/casbinrule.go
@@ -6,21 +6,37 @@ import (
"entgo.io/ent/schema/index"
)
+// MaxFieldLen is the maximum length of every policy column.
+//
+// The unique index below spans all seven policy columns. MySQL caps a single
+// InnoDB index key at 3072 bytes, and ent creates its tables as utf8mb4, where
+// a character costs up to 4 bytes. Leaving the columns at ent's default
+// varchar(255) would need 7*255*4 = 7140 bytes and the index would be rejected
+// with "Error 1071: Specified key was too long". At 100 the key needs
+// 7*100*4 = 2800 bytes, which fits, and it matches the varchar(100) used by
+// casbin's canonical SQL schema and by gorm-adapter.
+const MaxFieldLen = 100
+
// CasbinRule holds the schema definition for the CasbinRule entity.
type CasbinRule struct {
ent.Schema
}
// Fields of the CasbinRule.
+//
+// Every column is NOT NULL with an empty-string default. That matters for the
+// unique index: SQL treats NULLs as distinct from each other, so nullable
+// columns would silently defeat the constraint for every rule that leaves some
+// of V0..V5 unused.
func (CasbinRule) Fields() []ent.Field {
return []ent.Field{
- field.String("Ptype").Default(""),
- field.String("V0").Default(""),
- field.String("V1").Default(""),
- field.String("V2").Default(""),
- field.String("V3").Default(""),
- field.String("V4").Default(""),
- field.String("V5").Default(""),
+ field.String("Ptype").MaxLen(MaxFieldLen).Default(""),
+ field.String("V0").MaxLen(MaxFieldLen).Default(""),
+ field.String("V1").MaxLen(MaxFieldLen).Default(""),
+ field.String("V2").MaxLen(MaxFieldLen).Default(""),
+ field.String("V3").MaxLen(MaxFieldLen).Default(""),
+ field.String("V4").MaxLen(MaxFieldLen).Default(""),
+ field.String("V5").MaxLen(MaxFieldLen).Default(""),
}
}
@@ -29,7 +45,12 @@ func (CasbinRule) Edges() []ent.Edge {
return nil
}
-func (CasbinRule) Index() []ent.Index {
+// Indexes of the CasbinRule.
+//
+// The method must be named Indexes: ent discovers schema indexes through this
+// exact interface method, so a differently named method compiles fine and is
+// silently ignored by codegen.
+func (CasbinRule) Indexes() []ent.Index {
return []ent.Index{
index.Fields("Ptype", "V0", "V1", "V2", "V3", "V4",
"V5").Unique(),
}
diff --git a/go.sum b/go.sum
index 0d2d2ab..62f6047 100644
--- a/go.sum
+++ b/go.sum
@@ -35,6 +35,8 @@ github.com/google/uuid v1.6.0
h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod
h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl/v2 v2.24.0
h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
github.com/hashicorp/hcl/v2 v2.24.0/go.mod
h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
+github.com/inconshreveable/mousetrap v1.1.0
h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod
h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0
h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod
h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761
h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -49,16 +51,24 @@ github.com/kr/text v0.2.0
h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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-runewidth v0.0.9
h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
+github.com/mattn/go-runewidth v0.0.9/go.mod
h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
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=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod
h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
+github.com/olekukonko/tablewriter v0.0.5
h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
+github.com/olekukonko/tablewriter v0.0.5/go.mod
h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0
h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod
h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
+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/stretchr/objx v0.1.0/go.mod
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod
h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]