summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/spicedb/internal/datastore/common/schema.go
blob: 6e44d0ba34d4a7e4ff01fcd1c2cb392d7ba7c7c8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package common

import (
	sq "github.com/Masterminds/squirrel"

	"github.com/authzed/spicedb/pkg/datastore/options"
	"github.com/authzed/spicedb/pkg/datastore/queryshape"
	"github.com/authzed/spicedb/pkg/spiceerrors"
)

const (
	relationshipStandardColumnCount   = 6 // ColNamespace, ColObjectID, ColRelation, ColUsersetNamespace, ColUsersetObjectID, ColUsersetRelation
	relationshipCaveatColumnCount     = 2 // ColCaveatName, ColCaveatContext
	relationshipExpirationColumnCount = 1 // ColExpiration
	relationshipIntegrityColumnCount  = 3 // ColIntegrityKeyID, ColIntegrityHash, ColIntegrityTimestamp
)

// SchemaInformation holds the schema information from the SQL datastore implementation.
//
//go:generate go run github.com/ecordell/optgen -output zz_generated.schema_options.go . SchemaInformation
type SchemaInformation struct {
	RelationshipTableName string `debugmap:"visible"`

	ColNamespace        string `debugmap:"visible"`
	ColObjectID         string `debugmap:"visible"`
	ColRelation         string `debugmap:"visible"`
	ColUsersetNamespace string `debugmap:"visible"`
	ColUsersetObjectID  string `debugmap:"visible"`
	ColUsersetRelation  string `debugmap:"visible"`

	ColCaveatName    string `debugmap:"visible"`
	ColCaveatContext string `debugmap:"visible"`

	ColExpiration string `debugmap:"visible"`

	ColIntegrityKeyID     string `debugmap:"visible"`
	ColIntegrityHash      string `debugmap:"visible"`
	ColIntegrityTimestamp string `debugmap:"visible"`

	// Indexes are the indexes to use for this schema.
	Indexes []IndexDefinition `debugmap:"visible"`

	// PaginationFilterType is the type of pagination filter to use for this schema.
	PaginationFilterType PaginationFilterType `debugmap:"visible"`

	// PlaceholderFormat is the format of placeholders to use for this schema.
	PlaceholderFormat sq.PlaceholderFormat `debugmap:"visible"`

	// NowFunction is the function to use to get the current time in the datastore.
	NowFunction string `debugmap:"visible"`

	// ColumnOptimization is the optimization to use for columns in the schema, if any.
	ColumnOptimization ColumnOptimizationOption `debugmap:"visible"`

	// IntegrityEnabled is a flag to indicate if the schema has integrity columns.
	IntegrityEnabled bool `debugmap:"visible"`

	// ExpirationDisabled is a flag to indicate whether expiration support is disabled.
	ExpirationDisabled bool `debugmap:"visible"`

	// SortByResourceColumnOrder is the order of the resource columns in the schema to use
	// when sorting by resource. If unspecified, the default will be used.
	SortByResourceColumnOrder []string `debugmap:"visible"`

	// SortBySubjectColumnOrder is the order of the subject columns in the schema to use
	// when sorting by subject. If unspecified, the default will be used.
	SortBySubjectColumnOrder []string `debugmap:"visible"`
}

// expectedIndexesForShape returns the expected index names for a given query shape.
func (si SchemaInformation) expectedIndexesForShape(shape queryshape.Shape) options.SQLIndexInformation {
	expectedIndexes := options.SQLIndexInformation{}
	for _, index := range si.Indexes {
		if index.matchesShape(shape) {
			expectedIndexes.ExpectedIndexNames = append(expectedIndexes.ExpectedIndexNames, index.Name)
		}
	}
	return expectedIndexes
}

func (si SchemaInformation) debugValidate() {
	spiceerrors.DebugAssert(func() bool {
		si.mustValidate()
		return true
	}, "SchemaInformation failed to validate")
}

func (si SchemaInformation) sortByResourceColumnOrderColumns() []string {
	if len(si.SortByResourceColumnOrder) > 0 {
		return si.SortByResourceColumnOrder
	}

	return []string{
		si.ColNamespace,
		si.ColObjectID,
		si.ColRelation,
		si.ColUsersetNamespace,
		si.ColUsersetObjectID,
		si.ColUsersetRelation,
	}
}

func (si SchemaInformation) sortBySubjectColumnOrderColumns() []string {
	if len(si.SortBySubjectColumnOrder) > 0 {
		return si.SortBySubjectColumnOrder
	}

	return []string{
		si.ColUsersetNamespace,
		si.ColUsersetObjectID,
		si.ColUsersetRelation,
		si.ColNamespace,
		si.ColObjectID,
		si.ColRelation,
	}
}

func (si SchemaInformation) mustValidate() {
	if si.RelationshipTableName == "" {
		panic("RelationshipTableName is required")
	}

	if si.ColNamespace == "" {
		panic("ColNamespace is required")
	}

	if si.ColObjectID == "" {
		panic("ColObjectID is required")
	}

	if si.ColRelation == "" {
		panic("ColRelation is required")
	}

	if si.ColUsersetNamespace == "" {
		panic("ColUsersetNamespace is required")
	}

	if si.ColUsersetObjectID == "" {
		panic("ColUsersetObjectID is required")
	}

	if si.ColUsersetRelation == "" {
		panic("ColUsersetRelation is required")
	}

	if si.ColCaveatName == "" {
		panic("ColCaveatName is required")
	}

	if si.ColCaveatContext == "" {
		panic("ColCaveatContext is required")
	}

	if si.ColExpiration == "" {
		panic("ColExpiration is required")
	}

	if si.IntegrityEnabled {
		if si.ColIntegrityKeyID == "" {
			panic("ColIntegrityKeyID is required")
		}

		if si.ColIntegrityHash == "" {
			panic("ColIntegrityHash is required")
		}

		if si.ColIntegrityTimestamp == "" {
			panic("ColIntegrityTimestamp is required")
		}
	}

	if si.NowFunction == "" {
		panic("NowFunction is required")
	}

	if si.ColumnOptimization == ColumnOptimizationOptionUnknown {
		panic("ColumnOptimization is required")
	}

	if si.PaginationFilterType == PaginationFilterTypeUnknown {
		panic("PaginationFilterType is required")
	}

	if si.PlaceholderFormat == nil {
		panic("PlaceholderFormat is required")
	}
}