Documentation
¶
Overview ¶
Package sqlgen provides a domain-specific SQL DSL for generating Melange authorization queries. It models authorization concepts directly rather than generic SQL syntax.
Index ¶
- Variables
- func CollectFunctionNames(analyses []RelationAnalysis) []string
- func DirectCheck(input DirectCheckInput) (string, error)
- func ExclusionCheck(input ExclusionCheckInput) (string, error)
- func Ident(name string) string
- func ListObjectsComplexClosureQuery(input ListObjectsComplexClosureInput) (string, error)
- func ListObjectsCrossTypeTTUQuery(input ListObjectsCrossTypeTTUInput) (string, error)
- func ListObjectsDirectQuery(input ListObjectsDirectInput) (string, error)
- func ListObjectsIntersectionClosureQuery(functionName string) (string, error)
- func ListObjectsIntersectionClosureValidatedQuery(objectType, relation, functionName string) (string, error)
- func ListObjectsRecursiveTTUQuery(input ListObjectsRecursiveTTUInput) (string, error)
- func ListObjectsSelfCandidateQuery(input ListObjectsSelfCandidateInput) (string, error)
- func ListObjectsUsersetPatternComplexQuery(input ListObjectsUsersetPatternComplexInput) (string, error)
- func ListObjectsUsersetPatternSimpleQuery(input ListObjectsUsersetPatternSimpleInput) (string, error)
- func ListObjectsUsersetSubjectQuery(input ListObjectsUsersetSubjectInput) (string, error)
- func ListSubjectsComplexClosureQuery(input ListSubjectsComplexClosureInput) (string, error)
- func ListSubjectsDirectQuery(input ListSubjectsDirectInput) (string, error)
- func ListSubjectsIntersectionClosureQuery(functionName, subjectTypeExpr string) (string, error)
- func ListSubjectsIntersectionClosureValidatedQuery(objectType, relation, functionName, functionSubjectTypeExpr, ... string) (string, error)
- func ListSubjectsSelfCandidateQuery(input ListSubjectsSelfCandidateInput) (string, error)
- func ListSubjectsUsersetFilterQuery(input ListSubjectsUsersetFilterInput) (string, error)
- func ListSubjectsUsersetPatternComplexQuery(input ListSubjectsUsersetPatternComplexInput) (string, error)
- func ListSubjectsUsersetPatternRecursiveComplexQuery(input ListSubjectsUsersetPatternRecursiveComplexInput) (string, error)
- func ListSubjectsUsersetPatternSimpleQuery(input ListSubjectsUsersetPatternSimpleInput) (string, error)
- func RenderDSLExprs(exprs []Expr) []string
- func UsersetCheck(input UsersetCheckInput) (string, error)
- func UsersetSubjectComputedCheckQuery(input UsersetSubjectComputedCheckInput) (string, error)
- func UsersetSubjectSelfCheckQuery(input UsersetSubjectSelfCheckInput) (string, error)
- type Alias
- type AnchorPathStep
- type AndExpr
- type Bool
- type CheckFunctionData
- type CheckPermission
- type CheckPermissionCall
- type ClosureRow
- type Col
- type ComplexExclusionCheckData
- type ComplexUsersetCheckData
- type Concat
- type DirectCheckData
- type DirectCheckInput
- type DispatcherCase
- type DispatcherData
- type EmptyArray
- type Eq
- type ExcludedIntersectionGroup
- type ExcludedIntersectionPart
- type ExcludedParentRelation
- type ExclusionCheckData
- type ExclusionCheckInput
- type ExclusionConfig
- type Exists
- type Expr
- func CheckPermissionExprDSL(...) Expr
- func CheckPermissionInternalExprDSL(subjectTypeExpr, subjectIDExpr, relation, objectTypeExpr, objectIDExpr string, ...) Expr
- func NormalizedUsersetSubject(subjectID, relation Expr) Expr
- func SimpleExclusion(objectType, relation string, objectID, subjectType, subjectID Expr) Expr
- func SubjectIDMatch(column, subjectID Expr, allowWildcard bool) Expr
- type Func
- type GeneratedSQL
- type Gt
- type Gte
- type HasUserset
- type ImpliedFunctionCall
- type In
- type IndirectAnchorInfo
- type InlineSQLData
- type Int
- type IntersectionExclusionCheckData
- type IntersectionGroup
- type IntersectionGroupData
- type IntersectionGroupInfo
- type IntersectionPart
- type IntersectionPartData
- type IsNotNull
- type IsNull
- type IsWildcard
- type JoinClause
- type LateralFunction
- type ListAnchorPathStepData
- type ListDispatcherCase
- type ListDispatcherData
- type ListGeneratedSQL
- type ListIndirectAnchorData
- type ListObjectsBuilder
- type ListObjectsComplexClosureInput
- type ListObjectsCrossTypeTTUInput
- type ListObjectsDirectInput
- type ListObjectsFunctionData
- type ListObjectsRecursiveTTUInput
- type ListObjectsSelfCandidateInput
- type ListObjectsUsersetPatternComplexInput
- type ListObjectsUsersetPatternSimpleInput
- type ListObjectsUsersetSubjectInput
- type ListParentRelationData
- type ListSubjectsBuilder
- type ListSubjectsComplexClosureInput
- type ListSubjectsDirectInput
- type ListSubjectsFunctionData
- type ListSubjectsSelfCandidateInput
- type ListSubjectsUsersetFilterInput
- type ListSubjectsUsersetPatternComplexInput
- type ListSubjectsUsersetPatternRecursiveComplexInput
- type ListSubjectsUsersetPatternSimpleInput
- type ListUsersetPatternData
- type Lit
- type Lt
- type Lte
- type Ne
- type NoUserset
- type NotExists
- type NotExpr
- type Null
- type ObjectRef
- type OrExpr
- type Param
- type Paren
- type ParentRelationCheck
- type ParentRelationData
- type ParentRelationInfo
- type Raw
- type RelationAnalysis
- type RelationDefinition
- type RelationFeatures
- type SelectStmt
- type SubjectRef
- type SubjectTypeRef
- type SubstringUsersetRelation
- type TTUExclusionCheckData
- type TableExpr
- type TableRef
- type TupleQuery
- func (q *TupleQuery) Alias() string
- func (q *TupleQuery) Build() SelectStmt
- func (q *TupleQuery) Col(name string) Col
- func (q *TupleQuery) Distinct() *TupleQuery
- func (q *TupleQuery) ExistsSQL() string
- func (q *TupleQuery) InnerJoin(table, alias string, on ...Expr) *TupleQuery
- func (q *TupleQuery) JoinClosure(alias, closureValues string, on ...Expr) *TupleQuery
- func (q *TupleQuery) JoinRaw(joinType, tableExpr string, on ...Expr) *TupleQuery
- func (q *TupleQuery) JoinTuples(alias string, on ...Expr) *TupleQuery
- func (q *TupleQuery) LeftJoin(table, alias string, on ...Expr) *TupleQuery
- func (q *TupleQuery) Limit(n int) *TupleQuery
- func (q *TupleQuery) NotExistsSQL() string
- func (q *TupleQuery) ObjectType(t string) *TupleQuery
- func (q *TupleQuery) Relations(rels ...string) *TupleQuery
- func (q *TupleQuery) SQL() string
- func (q *TupleQuery) Select(cols ...string) *TupleQuery
- func (q *TupleQuery) SelectCol(columns ...string) *TupleQuery
- func (q *TupleQuery) SelectExpr(exprs ...Expr) *TupleQuery
- func (q *TupleQuery) Where(exprs ...Expr) *TupleQuery
- func (q *TupleQuery) WhereHasUserset() *TupleQuery
- func (q *TupleQuery) WhereNoUserset() *TupleQuery
- func (q *TupleQuery) WhereObject(ref ObjectRef) *TupleQuery
- func (q *TupleQuery) WhereObjectID(id Expr) *TupleQuery
- func (q *TupleQuery) WhereSubject(ref SubjectRef) *TupleQuery
- func (q *TupleQuery) WhereSubjectID(id Expr, allowWildcard bool) *TupleQuery
- func (q *TupleQuery) WhereSubjectType(t Expr) *TupleQuery
- func (q *TupleQuery) WhereSubjectTypeIn(types ...string) *TupleQuery
- func (q *TupleQuery) WhereUsersetRelation(rel string) *TupleQuery
- type TypeDefinition
- type UsersetCheckData
- type UsersetCheckInput
- type UsersetObjectID
- type UsersetPattern
- type UsersetRelation
- type UsersetSubjectComputedCheckInput
- type UsersetSubjectSelfCheckInput
- type ValuesTable
Constants ¶
This section is empty.
Variables ¶
var ( SubjectType = Param("p_subject_type") SubjectID = Param("p_subject_id") ObjectType = Param("p_object_type") ObjectID = Param("p_object_id") Visited = Param("p_visited") )
Common parameter constants.
var ComputeRelationClosure = schema.ComputeRelationClosure
Function alias for schema.ComputeRelationClosure.
Functions ¶
func CollectFunctionNames ¶
func CollectFunctionNames(analyses []RelationAnalysis) []string
CollectFunctionNames returns all function names that will be generated for the given analyses. This is used for migration tracking and orphan detection.
The returned list includes:
- Specialized check functions: check_{type}_{relation}
- No-wildcard check variants: check_{type}_{relation}_no_wildcard
- Specialized list functions: list_{type}_{relation}_objects, list_{type}_{relation}_subjects
- Dispatcher functions (always included)
func DirectCheck ¶
func DirectCheck(input DirectCheckInput) (string, error)
func ExclusionCheck ¶
func ExclusionCheck(input ExclusionCheckInput) (string, error)
func Ident ¶
Ident sanitizes an identifier for use in SQL. Replaces non-alphanumeric characters with underscores.
func ListObjectsComplexClosureQuery ¶
func ListObjectsComplexClosureQuery(input ListObjectsComplexClosureInput) (string, error)
func ListObjectsCrossTypeTTUQuery ¶
func ListObjectsCrossTypeTTUQuery(input ListObjectsCrossTypeTTUInput) (string, error)
func ListObjectsDirectQuery ¶
func ListObjectsDirectQuery(input ListObjectsDirectInput) (string, error)
func ListObjectsRecursiveTTUQuery ¶
func ListObjectsRecursiveTTUQuery(input ListObjectsRecursiveTTUInput) (string, error)
func ListObjectsSelfCandidateQuery ¶
func ListObjectsSelfCandidateQuery(input ListObjectsSelfCandidateInput) (string, error)
func ListObjectsUsersetPatternComplexQuery ¶
func ListObjectsUsersetPatternComplexQuery(input ListObjectsUsersetPatternComplexInput) (string, error)
func ListObjectsUsersetPatternSimpleQuery ¶
func ListObjectsUsersetPatternSimpleQuery(input ListObjectsUsersetPatternSimpleInput) (string, error)
func ListObjectsUsersetSubjectQuery ¶
func ListObjectsUsersetSubjectQuery(input ListObjectsUsersetSubjectInput) (string, error)
func ListSubjectsComplexClosureQuery ¶
func ListSubjectsComplexClosureQuery(input ListSubjectsComplexClosureInput) (string, error)
func ListSubjectsDirectQuery ¶
func ListSubjectsDirectQuery(input ListSubjectsDirectInput) (string, error)
func ListSubjectsSelfCandidateQuery ¶
func ListSubjectsSelfCandidateQuery(input ListSubjectsSelfCandidateInput) (string, error)
func ListSubjectsUsersetFilterQuery ¶
func ListSubjectsUsersetFilterQuery(input ListSubjectsUsersetFilterInput) (string, error)
func ListSubjectsUsersetPatternComplexQuery ¶
func ListSubjectsUsersetPatternComplexQuery(input ListSubjectsUsersetPatternComplexInput) (string, error)
func ListSubjectsUsersetPatternRecursiveComplexQuery ¶
func ListSubjectsUsersetPatternRecursiveComplexQuery(input ListSubjectsUsersetPatternRecursiveComplexInput) (string, error)
func ListSubjectsUsersetPatternSimpleQuery ¶
func ListSubjectsUsersetPatternSimpleQuery(input ListSubjectsUsersetPatternSimpleInput) (string, error)
func RenderDSLExprs ¶
RenderDSLExprs converts a slice of DSL expressions to SQL strings.
func UsersetCheck ¶
func UsersetCheck(input UsersetCheckInput) (string, error)
func UsersetSubjectComputedCheckQuery ¶
func UsersetSubjectComputedCheckQuery(input UsersetSubjectComputedCheckInput) (string, error)
func UsersetSubjectSelfCheckQuery ¶
func UsersetSubjectSelfCheckQuery(input UsersetSubjectSelfCheckInput) (string, error)
Types ¶
type Alias ¶
Alias wraps an expression with an alias (expr AS alias).
type AnchorPathStep ¶
type AnchorPathStep struct {
// Type is either "ttu" or "userset"
Type string
// For TTU patterns (e.g., "viewer from parent"):
// LinkingRelation is "parent" (the relation that links to the parent object)
// TargetType is the first parent object type found with an anchor (e.g., "folder")
// TargetRelation is the relation to check on the parent (e.g., "viewer")
// AllTargetTypes contains ALL object types that the linking relation can point to
// when each type has the target relation with direct grants. This is used for
// generating UNION queries when parent can be multiple types (e.g., [document, folder]).
// RecursiveTypes contains object types where the target relation is recursive
// (same type as the object type being checked). These require check_permission_internal
// instead of list function composition to handle the recursion correctly.
LinkingRelation string
TargetType string
TargetRelation string
AllTargetTypes []string
RecursiveTypes []string
// For userset patterns (e.g., [group#member]):
// SubjectType is "group"
// SubjectRelation is "member"
SubjectType string
SubjectRelation string
}
AnchorPathStep represents one step in the path from a relation to its anchor. Steps can be either TTU (tuple-to-userset) or userset patterns.
type AndExpr ¶
type AndExpr struct {
Exprs []Expr
}
AndExpr represents a logical AND of multiple expressions.
type CheckFunctionData ¶
type CheckFunctionData struct {
ObjectType string // The authorization object type (e.g., "document", "folder")
Relation string // The relation name (e.g., "viewer", "editor")
FunctionName string // The generated function name (e.g., "check_document_viewer")
// InternalCheckFunctionName is the dispatcher internal function name to call
// for recursive or complex checks.
InternalCheckFunctionName string
FeaturesString string // Human-readable list of enabled features for SQL comments
ClosureValues string // Inline SQL VALUES for closure lookups, eliminates JOIN
UsersetValues string // Inline SQL VALUES for userset patterns, eliminates JOIN
// Feature flags
HasDirect bool
HasImplied bool
HasWildcard bool
HasUserset bool
HasRecursive bool
HasExclusion bool
HasIntersection bool
// HasStandaloneAccess is true if the relation has access paths outside of intersections.
// When false and HasIntersection is true, the only access is through intersection groups.
// For example, "viewer: [user] and writer" has NO standalone access - the [user] is
// inside the intersection. But "viewer: [user] or (writer and editor)" HAS standalone
// access via the [user] path.
HasStandaloneAccess bool
// Pre-rendered SQL fragments
DirectCheck string // Pre-rendered SQL EXISTS for direct tuple lookup
UsersetCheck string // Pre-rendered SQL EXISTS for userset membership checks
ExclusionCheck string // Pre-rendered SQL EXISTS for exclusion (denial) checks
AccessChecks string // All access paths OR'd together for the final permission decision
// For recursive (TTU) patterns
ParentRelations []ParentRelationData // TTU patterns: parent object relations to check recursively
// For implied relations that need function calls
ImpliedFunctionCalls []ImpliedFunctionCall // Complex closure relations requiring function calls
// For intersection patterns - each group is AND'd, groups are OR'd
IntersectionGroups []IntersectionGroupData // AND groups where all parts must be satisfied
}
CheckFunctionData contains data for rendering check function templates.
type CheckPermission ¶
type CheckPermission struct {
Subject SubjectRef
Relation string
Object ObjectRef
Visited Expr // nil for default empty array
ExpectAllow bool // true = "= 1", false = "= 0"
}
CheckPermission represents a call to check_permission_internal. This is the core permission check expression used in queries.
func CheckAccess ¶
func CheckAccess(relation, objectType string, objectID Expr) CheckPermission
CheckAccess creates a CheckPermission that expects access to be allowed. Uses SubjectParams() for subject and the given parameters for object.
func CheckNoAccess ¶
func CheckNoAccess(relation, objectType string, objectID Expr) CheckPermission
CheckNoAccess creates a CheckPermission that expects access to be denied. Uses SubjectParams() for subject and the given parameters for object.
func (CheckPermission) SQL ¶
func (c CheckPermission) SQL() string
SQL renders the check_permission_internal call with comparison.
type CheckPermissionCall ¶
type CheckPermissionCall struct {
FunctionName string
Subject SubjectRef
Relation string
Object ObjectRef
ExpectAllow bool
}
CheckPermissionCall represents a call to a custom permission check function. This is useful for calling specialized generated functions.
func (CheckPermissionCall) SQL ¶
func (c CheckPermissionCall) SQL() string
SQL renders the function call with comparison.
type ClosureRow ¶
type ClosureRow = schema.ClosureRow
Type aliases for schema types used in analysis. This avoids prefixing every usage with "schema." throughout this file.
type ComplexExclusionCheckData ¶
type ComplexExclusionCheckData struct {
ObjectType string
ExcludedRelation string
InternalCheckFunctionName string
}
ComplexExclusionCheckData contains data for rendering complex exclusion checks. These use check_permission_internal instead of direct tuple lookup.
type ComplexUsersetCheckData ¶
type ComplexUsersetCheckData struct {
ObjectType string
Relation string
SubjectType string
SubjectRelation string
InternalCheckFunctionName string
}
ComplexUsersetCheckData contains data for rendering complex userset check template. Used when the userset closure contains relations with complex features.
type DirectCheckData ¶
type DirectCheckData struct {
ObjectType string
RelationList string
SubjectTypeFilter string // e.g., "'user', 'employee'" - allowed subject types
SubjectIDCheck string
}
DirectCheckData contains data for rendering direct check template.
type DirectCheckInput ¶
type DispatcherCase ¶
DispatcherCase represents a single CASE WHEN branch in the dispatcher.
type DispatcherData ¶
type DispatcherData struct {
FunctionName string
HasSpecializedFunctions bool
Cases []DispatcherCase
}
DispatcherData contains data for rendering dispatcher template.
type EmptyArray ¶
type EmptyArray struct{}
EmptyArray represents an empty text array (ARRAY[]::TEXT[]).
type ExcludedIntersectionGroup ¶
type ExcludedIntersectionGroup struct {
Parts []ExcludedIntersectionPart
}
ExcludedIntersectionGroup represents a complete intersection exclusion.
type ExcludedIntersectionPart ¶
type ExcludedIntersectionPart struct {
Relation string
ExcludedRelation string
ParentRelation *ExcludedParentRelation
}
ExcludedIntersectionPart represents one part of an intersection exclusion.
type ExcludedParentRelation ¶
type ExcludedParentRelation struct {
Relation string
LinkingRelation string
AllowedLinkingTypes []string
}
ExcludedParentRelation represents a TTU exclusion pattern.
type ExclusionCheckData ¶
ExclusionCheckData contains data for rendering exclusion check template.
type ExclusionCheckInput ¶
type ExclusionConfig ¶
type ExclusionConfig struct {
ObjectType string
// References to use in exclusion predicates
ObjectIDExpr Expr
SubjectTypeExpr Expr
SubjectIDExpr Expr
// Exclusion types
SimpleExcludedRelations []string
ComplexExcludedRelations []string
ExcludedParentRelations []ExcludedParentRelation
ExcludedIntersection []ExcludedIntersectionGroup
}
ExclusionConfig holds all exclusion rules for a query.
func (ExclusionConfig) BuildPredicates ¶
func (c ExclusionConfig) BuildPredicates() []Expr
BuildPredicates returns the exclusion predicates as Expr slices.
func (ExclusionConfig) HasExclusions ¶
func (c ExclusionConfig) HasExclusions() bool
HasExclusions returns true if any exclusion rules are configured.
type Exists ¶
type Exists struct {
Query interface{ SQL() string }
}
Exists represents an EXISTS subquery.
func ExistsExpr ¶
func ExistsExpr(stmt SelectStmt) Exists
ExistsExpr creates an Exists expression from a SelectStmt. Use when you need EXISTS as part of a larger expression (e.g., in WHERE clause).
type Expr ¶
type Expr interface {
SQL() string
}
Expr is the interface that all SQL expression types implement.
func CheckPermissionExprDSL ¶
func CheckPermissionExprDSL(functionName, subjectTypeExpr, subjectIDExpr, relation, objectTypeExpr, objectIDExpr string, expect bool) Expr
CheckPermissionExprDSL returns a DSL expression for a check_permission call.
func CheckPermissionInternalExprDSL ¶
func CheckPermissionInternalExprDSL(subjectTypeExpr, subjectIDExpr, relation, objectTypeExpr, objectIDExpr string, expect bool) Expr
CheckPermissionInternalExprDSL returns a DSL expression for a check_permission_internal call.
func NormalizedUsersetSubject ¶
NormalizedUsersetSubject creates a normalized userset subject reference. Takes the object_id from subjectID and combines with the given relation. Example: NormalizedUsersetSubject(Col{Column: "subject_id"}, Raw("v_filter_relation")) SQL: split_part(subject_id, '#', 1) || '#' || v_filter_relation
func SimpleExclusion ¶
SimpleExclusion creates a NOT EXISTS exclusion for a simple "but not" rule.
func SubjectIDMatch ¶
SubjectIDMatch creates a condition for matching subject IDs. If allowWildcard is true, matches either exact ID or wildcard. If allowWildcard is false, matches exact ID and excludes wildcards.
type GeneratedSQL ¶
type GeneratedSQL struct {
// Functions contains CREATE OR REPLACE FUNCTION statements
// for each specialized check function.
Functions []string
// NoWildcardFunctions contains CREATE OR REPLACE FUNCTION statements
// for no-wildcard variants of each specialized check function.
NoWildcardFunctions []string
// Dispatcher contains the check_permission dispatcher function
// that routes to specialized functions.
Dispatcher string
// DispatcherNoWildcard contains the check_permission_no_wildcard dispatcher.
DispatcherNoWildcard string
}
GeneratedSQL contains all SQL generated for a schema. This is applied atomically during migration.
func GenerateSQL ¶
func GenerateSQL(analyses []RelationAnalysis, inline InlineSQLData) (GeneratedSQL, error)
GenerateSQL generates specialized SQL functions for all relations. The generated SQL includes:
- Per-relation check functions (check_{type}_{relation})
- A dispatcher that routes check_permission to specialized functions
type HasUserset ¶
type HasUserset struct {
Source Expr
}
HasUserset checks if an expression contains a userset marker (#). Returns true if the expression contains '#'. Uses: position('#' in source) > 0
type ImpliedFunctionCall ¶
type ImpliedFunctionCall struct {
FunctionName string // Function to call for this implied relation (e.g., "check_document_editor")
}
ImpliedFunctionCall represents a function call to a complex implied relation. Used when an implied relation has exclusions and can't use simple tuple lookup.
type IndirectAnchorInfo ¶
type IndirectAnchorInfo struct {
// Path describes the traversal from this relation to the anchor.
// For "document.viewer: viewer from folder" where folder.viewer: [user],
// Path would contain one TTU step pointing to folder.viewer.
// For multi-hop chains, Path contains multiple steps.
Path []AnchorPathStep
// AnchorType and AnchorRelation identify the relation with direct grants.
// This is the final destination of the path - a relation that has HasDirect.
AnchorType string
AnchorRelation string
}
IndirectAnchorInfo describes how to reach a relation with direct grants when the relation itself has no direct/implied access paths. This enables list generation for "pure" patterns like pure TTU or pure userset by tracing through to find an anchor relation that has [user] or similar direct grants.
For example, for "document.viewer: viewer from folder" where folder.viewer: [user], the IndirectAnchor would point to folder.viewer with a TTU path step.
type InlineSQLData ¶
type InlineSQLData struct {
// ClosureValues contains tuples of (object_type, relation, satisfying_relation).
ClosureValues string
// UsersetValues contains tuples of (object_type, relation, subject_type, subject_relation).
UsersetValues string
}
InlineSQLData contains SQL VALUES payloads that replace database-backed model tables. Rationale: Model data is inlined into SQL VALUES clauses rather than querying database tables. This eliminates the need for persistent melange_model tables and ensures generated functions are self-contained. When the schema changes, migration regenerates all functions with updated inline data. This approach trades function size for runtime simplicity and removes a JOIN from every check.
func BuildInlineSQLData ¶
func BuildInlineSQLData(closureRows []ClosureRow, analyses []RelationAnalysis) InlineSQLData
BuildInlineSQLData exposes inline SQL generation for tools and tests.
type IntersectionExclusionCheckData ¶
type IntersectionExclusionCheckData struct {
ObjectType string
Parts []string // Relations that must ALL be satisfied for exclusion to apply
}
IntersectionExclusionCheckData contains data for rendering intersection exclusion checks. These check "but not (A and B)" patterns by ANDing together check_permission_internal calls.
type IntersectionGroup ¶
type IntersectionGroup = schema.IntersectionGroup
Type aliases for schema types used in analysis. This avoids prefixing every usage with "schema." throughout this file.
type IntersectionGroupData ¶
type IntersectionGroupData struct {
Parts []IntersectionPartData // Individual checks within this AND group
}
IntersectionGroupData contains data for a single intersection group. All parts within a group must be satisfied (AND semantics).
type IntersectionGroupInfo ¶
type IntersectionGroupInfo struct {
Parts []IntersectionPart
}
IntersectionGroupInfo represents a group of parts that must ALL be satisfied (AND). Multiple groups are OR'd together.
type IntersectionPart ¶
type IntersectionPart struct {
IsThis bool // [user] - direct assignment check on the same relation
HasWildcard bool // For IsThis parts: whether direct assignment allows wildcards
Relation string // Relation to check
ExcludedRelation string // For nested exclusions like "editor but not owner"
ParentRelation *ParentRelationInfo // For tuple-to-userset in intersection
}
IntersectionPart represents one part of an intersection check. For "writer and (editor but not owner)", we'd have:
- {Relation: "writer"}
- {Relation: "editor", ExcludedRelation: "owner"}
type IntersectionPartData ¶
type IntersectionPartData struct {
// FunctionName is the check function to call (e.g., "check_document_writer")
FunctionName string
// IsThis is true if this part is a self-reference ([user] pattern)
// When true, we check for a direct tuple on the relation being defined
IsThis bool
// ThisHasWildcard is true if this "This" part allows wildcard tuples.
// This is only relevant when IsThis is true. It reflects whether the relation's
// own direct subject types allow wildcards, NOT whether the relation's overall
// HasWildcard flag is set (which may include wildcards from closure relations).
ThisHasWildcard bool
// HasExclusion is true if this part has a nested exclusion (e.g., "editor but not owner")
HasExclusion bool
// ExcludedRelation is the relation to exclude (for nested exclusions)
ExcludedRelation string
// IsTTU is true if this part is a tuple-to-userset pattern
IsTTU bool
// TTULinkingRelation is the linking relation for TTU patterns (e.g., "parent")
TTULinkingRelation string
// TTURelation is the relation to check on the parent for TTU patterns
TTURelation string
}
IntersectionPartData contains data for a single part of an intersection.
type IsWildcard ¶
type IsWildcard struct {
Source Expr
}
IsWildcard checks if an expression equals the wildcard value "*".
type JoinClause ¶
type JoinClause struct {
Type string // "INNER", "LEFT", etc.
Table string // Deprecated: use TableExpr instead
TableExpr TableExpr // Preferred: typed table expression
Alias string // Deprecated: use TableExpr's alias instead
On Expr
}
JoinClause represents a SQL JOIN clause.
type LateralFunction ¶
LateralFunction represents a LATERAL function call in a JOIN. Example: LateralFunction{Name: "list_doc_viewer_subjects", Args: []Expr{...}, Alias: "s"} Renders: LATERAL list_doc_viewer_subjects(...) AS s
func (LateralFunction) SQL ¶
func (l LateralFunction) SQL() string
SQL renders the LATERAL function expression.
func (LateralFunction) TableAlias ¶
func (l LateralFunction) TableAlias() string
TableAlias implements TableExpr.
func (LateralFunction) TableSQL ¶
func (l LateralFunction) TableSQL() string
TableSQL implements TableExpr.
type ListAnchorPathStepData ¶
type ListAnchorPathStepData struct {
Type string // "ttu" or "userset"
// For TTU steps (e.g., "viewer from parent"):
LinkingRelation string // "parent"
TargetType string // "folder" (first type with direct anchor)
TargetRelation string // "viewer"
AllTargetTypes []string // All types with direct anchor (e.g., ["document", "folder"])
RecursiveTypes []string // Types needing check_permission_internal (same-type recursive TTU)
// For userset steps (e.g., [group#member]):
SubjectType string // "group"
SubjectRelation string // "member"
SatisfyingRelationsList string // SQL-formatted satisfying relations
HasWildcard bool // Whether membership allows wildcards
}
ListAnchorPathStepData contains data for rendering one step in an indirect anchor path.
type ListDispatcherCase ¶
ListDispatcherCase represents a single routing case in the list dispatcher.
type ListDispatcherData ¶
type ListDispatcherData struct {
// HasSpecializedFunctions is true if any specialized list functions were generated.
HasSpecializedFunctions bool
// Cases contains the routing cases for specialized functions.
Cases []ListDispatcherCase
}
ListDispatcherData contains data for rendering list dispatcher templates.
type ListGeneratedSQL ¶
type ListGeneratedSQL struct {
// ListObjectsFunctions contains CREATE OR REPLACE FUNCTION statements
// for each specialized list_objects function (list_{type}_{relation}_objects).
ListObjectsFunctions []string
// ListSubjectsFunctions contains CREATE OR REPLACE FUNCTION statements
// for each specialized list_subjects function (list_{type}_{relation}_subjects).
ListSubjectsFunctions []string
// ListObjectsDispatcher contains the list_accessible_objects dispatcher function
// that routes to specialized functions or falls back to generic.
ListObjectsDispatcher string
// ListSubjectsDispatcher contains the list_accessible_subjects dispatcher function
// that routes to specialized functions or falls back to generic.
ListSubjectsDispatcher string
}
ListGeneratedSQL contains all SQL generated for list functions. This is separate from check function generation to keep concerns isolated. Applied atomically during migration alongside check functions.
func GenerateListSQL ¶
func GenerateListSQL(analyses []RelationAnalysis, inline InlineSQLData) (ListGeneratedSQL, error)
GenerateListSQL generates specialized SQL functions for list operations. The generated SQL includes:
- Per-relation list_objects functions (list_{type}_{relation}_objects)
- Per-relation list_subjects functions (list_{type}_{relation}_subjects)
- Dispatchers that route to specialized functions or fall back to generic
During the migration phase, relations that cannot be generated will use the generic list functions as fallback. As more patterns are supported, the CanGenerateList criteria will be relaxed.
type ListIndirectAnchorData ¶
type ListIndirectAnchorData struct {
// Path steps from this relation to the anchor
Path []ListAnchorPathStepData
// First step's target function (used for composition)
// For multi-hop chains, we compose with the first step's target, not the anchor.
// e.g., for job.can_read -> permission.assignee -> role.assignee, we call
// list_permission_assignee_objects (first step's target), not list_role_assignee_objects (anchor).
FirstStepTargetFunctionName string // e.g., "list_permission_assignee_objects"
// Anchor relation info (end of the chain)
AnchorType string // Type of anchor relation (e.g., "folder")
AnchorRelation string // Anchor relation name (e.g., "viewer")
AnchorFunctionName string // Name of anchor's list function (e.g., "list_folder_viewer_objects")
AnchorSubjectTypes string // SQL-formatted allowed subject types from anchor
AnchorHasWildcard bool // Whether anchor supports wildcards
SatisfyingRelationsList string // SQL-formatted list of relations that satisfy the anchor
}
ListIndirectAnchorData contains data for rendering composed access patterns in list templates. This is used when a relation has no direct/implied access but can reach subjects through TTU or userset patterns to an anchor relation that has direct grants.
type ListObjectsBuilder ¶
type ListObjectsBuilder struct {
// contains filtered or unexported fields
}
ListObjectsBuilder generates list_objects functions using a feature-driven approach rather than template-based switch statements.
func NewListObjectsBuilder ¶
func NewListObjectsBuilder(a RelationAnalysis, inline InlineSQLData) *ListObjectsBuilder
NewListObjectsBuilder creates a builder for generating list_objects functions.
func (*ListObjectsBuilder) Build ¶
func (b *ListObjectsBuilder) Build() (string, error)
Build generates the complete list_objects function SQL.
type ListObjectsComplexClosureInput ¶
type ListObjectsComplexClosureInput struct {
ObjectType string
Relation string
AllowedSubjectTypes []string
AllowWildcard bool
Exclusions ExclusionConfig
}
type ListObjectsCrossTypeTTUInput ¶
type ListObjectsCrossTypeTTUInput struct {
ObjectType string
LinkingRelation string
Relation string
CrossTypes []string
Exclusions ExclusionConfig
}
type ListObjectsDirectInput ¶
type ListObjectsDirectInput struct {
ObjectType string
Relations []string
AllowedSubjectTypes []string
AllowWildcard bool
Exclusions ExclusionConfig
}
type ListObjectsFunctionData ¶
type ListObjectsFunctionData struct {
ObjectType string
Relation string
FunctionName string
FeaturesString string
ClosureValues string
// MaxUsersetDepth is the maximum userset chain depth reachable from this relation.
// Used by depth-exceeded template to report the actual depth in error messages.
MaxUsersetDepth int
// ExceedsDepthLimit is true if MaxUsersetDepth >= 25.
// Routes to depth-exceeded template which immediately raises M2002.
ExceedsDepthLimit bool
// RelationList is a SQL-formatted list of simple closure relations to check.
// e.g., "'viewer', 'editor', 'owner'" - only relations that can use tuple lookup
RelationList string
// ComplexClosureRelations are closure relations that need check_permission_internal.
// These have exclusions or other complex features that can't be resolved via tuple lookup.
ComplexClosureRelations []string
// IntersectionClosureRelations are closure relations that have intersection patterns
// and are list-generatable. These need to be composed with their list function.
IntersectionClosureRelations []string
// SubjectIDCheck is the SQL fragment for checking subject_id with wildcard support.
// e.g., "(t.subject_id = p_subject_id OR t.subject_id = '*')"
SubjectIDCheck string
// AllowedSubjectTypes is a SQL-formatted list of allowed subject types.
// e.g., "'user', 'employee'" - used to enforce model type restrictions.
AllowedSubjectTypes string
// Exclusion-related fields (Phase 3)
HasExclusion bool // true if this relation has exclusion patterns
// SimpleExcludedRelations are excluded relations that can use direct tuple lookup.
// These are relations without userset, TTU, exclusion, intersection, or implied closure.
SimpleExcludedRelations []string
// ComplexExcludedRelations are excluded relations that need check_permission_internal.
// These have userset, TTU, intersection, exclusion, or implied closure.
ComplexExcludedRelations []string
// ExcludedParentRelations are TTU exclusions like "but not viewer from parent".
ExcludedParentRelations []ParentRelationInfo
// ExcludedIntersectionGroups are intersection exclusions like "but not (editor and owner)".
ExcludedIntersectionGroups []IntersectionGroupInfo
// Userset-related fields (Phase 4)
HasUserset bool // true if this relation has userset patterns
UsersetPatterns []ListUsersetPatternData // [group#member] patterns for UNION expansion
// TTU/Recursive-related fields (Phase 5)
ParentRelations []ListParentRelationData // TTU patterns like "viewer from parent"
// SelfReferentialLinkingRelations is a SQL-formatted list of linking relations
// from self-referential TTU patterns. Used for depth checking in recursive CTE.
// e.g., "'parent', 'folder'" when there are TTU patterns viewer from parent, viewer from folder
SelfReferentialLinkingRelations string
// Intersection-related fields (Phase 6)
HasIntersection bool // true if this relation has intersection patterns
IntersectionGroups []IntersectionGroupInfo // Intersection groups for list functions
HasStandaloneAccess bool // true if there are access paths outside intersections
// Phase 8: Indirect anchor for composed access patterns
HasIndirectAnchor bool // true if access is via indirect anchor
IndirectAnchor *ListIndirectAnchorData // Anchor info for composed templates
// Phase 9B: Self-referential userset patterns
HasSelfReferentialUserset bool // true if any userset pattern references same type/relation
}
ListObjectsFunctionData contains data for rendering list_objects function templates.
type ListObjectsRecursiveTTUInput ¶
type ListObjectsRecursiveTTUInput struct {
ObjectType string
LinkingRelations []string
Exclusions ExclusionConfig
}
type ListObjectsUsersetSubjectInput ¶
type ListObjectsUsersetSubjectInput struct {
ObjectType string
Relations []string
ClosureValues string
Exclusions ExclusionConfig
}
type ListParentRelationData ¶
type ListParentRelationData struct {
Relation string // Relation to check on parent (e.g., "viewer")
LinkingRelation string // Relation that links to parent (e.g., "parent")
AllowedLinkingTypes string // SQL-formatted list of parent types (e.g., "'folder', 'org'")
ParentType string // First allowed linking type (for self-referential check)
IsSelfReferential bool // True if any parent type equals the object type
// CrossTypeLinkingTypes is a SQL-formatted list of linking types that are NOT self-referential.
// When a parent relation allows both self-referential and cross-type links (e.g., [folder, document]
// for document.parent), this contains only the cross-type entries (e.g., "'folder'").
// Used to generate check_permission_internal calls for cross-type parents even when
// IsSelfReferential is true for the same linking relation.
CrossTypeLinkingTypes string
HasCrossTypeLinks bool // True if CrossTypeLinkingTypes is non-empty
}
ListParentRelationData contains data for rendering TTU pattern expansion in list templates. For a pattern like "viewer from parent", this represents the parent traversal.
type ListSubjectsBuilder ¶
type ListSubjectsBuilder struct {
// contains filtered or unexported fields
}
ListSubjectsBuilder generates list_subjects functions using a feature-driven approach. Unlike list_objects, list_subjects has TWO code paths: 1. Userset filter path - when p_subject_type contains '#' (e.g., "group#member") 2. Regular path - when p_subject_type is a simple type (e.g., "user")
func NewListSubjectsBuilder ¶
func NewListSubjectsBuilder(a RelationAnalysis, inline InlineSQLData) *ListSubjectsBuilder
NewListSubjectsBuilder creates a builder for generating list_subjects functions.
func (*ListSubjectsBuilder) Build ¶
func (b *ListSubjectsBuilder) Build() (string, error)
Build generates the complete list_subjects function SQL.
type ListSubjectsDirectInput ¶
type ListSubjectsFunctionData ¶
type ListSubjectsFunctionData struct {
ObjectType string
Relation string
FunctionName string
FeaturesString string
ClosureValues string
// MaxUsersetDepth is the maximum userset chain depth reachable from this relation.
// Used by depth-exceeded template to report the actual depth in error messages.
MaxUsersetDepth int
// ExceedsDepthLimit is true if MaxUsersetDepth >= 25.
// Routes to depth-exceeded template which immediately raises M2002.
ExceedsDepthLimit bool
// RelationList is a SQL-formatted list of simple closure relations to check.
RelationList string
// AllSatisfyingRelations is a SQL-formatted list of ALL relations that satisfy this relation.
// Includes both simple and complex closure relations. Used by userset filter case.
// e.g., "'can_view', 'viewer'" when viewer implies can_view
AllSatisfyingRelations string
// ComplexClosureRelations are closure relations that need check_permission_internal.
ComplexClosureRelations []string
// IntersectionClosureRelations are closure relations that have intersection patterns
// and are list-generatable. These need to be composed with their list function.
IntersectionClosureRelations []string
// AllowedSubjectTypes is a SQL-formatted list of allowed subject types.
// e.g., "'user', 'employee'" - used to enforce model type restrictions.
AllowedSubjectTypes string
// HasWildcard is true if the model allows wildcard subjects.
// When false, wildcard tuples (subject_id = '*') should be excluded from results.
HasWildcard bool
// Exclusion-related fields (Phase 3)
HasExclusion bool // true if this relation has exclusion patterns
// SimpleExcludedRelations are excluded relations that can use direct tuple lookup.
SimpleExcludedRelations []string
// ComplexExcludedRelations are excluded relations that need check_permission_internal.
ComplexExcludedRelations []string
// ExcludedParentRelations are TTU exclusions like "but not viewer from parent".
ExcludedParentRelations []ParentRelationInfo
// ExcludedIntersectionGroups are intersection exclusions like "but not (editor and owner)".
ExcludedIntersectionGroups []IntersectionGroupInfo
// Userset-related fields (Phase 4)
HasUserset bool // true if this relation has userset patterns
UsersetPatterns []ListUsersetPatternData // [group#member] patterns for expansion
// TTU/Recursive-related fields (Phase 5)
ParentRelations []ListParentRelationData // TTU patterns like "viewer from parent"
// Intersection-related fields (Phase 6)
HasIntersection bool // true if this relation has intersection patterns
IntersectionGroups []IntersectionGroupInfo // Intersection groups for list functions
// Phase 8: Indirect anchor for composed access patterns
HasIndirectAnchor bool // true if access is via indirect anchor
IndirectAnchor *ListIndirectAnchorData // Anchor info for composed templates
// Phase 9B: Self-referential userset patterns
HasSelfReferentialUserset bool // true if any userset pattern references same type/relation
}
ListSubjectsFunctionData contains data for rendering list_subjects function templates.
type ListSubjectsUsersetPatternRecursiveComplexInput ¶
type ListSubjectsUsersetPatternRecursiveComplexInput struct {
ObjectType string
SubjectType string
SubjectRelation string
SourceRelations []string
ObjectIDExpr string
SubjectTypeExpr string
AllowedSubjectTypes []string
ExcludeWildcard bool
IsClosurePattern bool
SourceRelation string
Exclusions ExclusionConfig
}
type ListSubjectsUsersetPatternSimpleInput ¶
type ListSubjectsUsersetPatternSimpleInput struct {
ObjectType string
SubjectType string
SubjectRelation string
SourceRelations []string
SatisfyingRelations []string
ObjectIDExpr string
SubjectTypeExpr string
AllowedSubjectTypes []string
ExcludeWildcard bool
IsClosurePattern bool
SourceRelation string
Exclusions ExclusionConfig
}
type ListUsersetPatternData ¶
type ListUsersetPatternData struct {
SubjectType string // e.g., "group"
SubjectRelation string // e.g., "member"
// SatisfyingRelationsList is a SQL-formatted list of relations that satisfy SubjectRelation.
// e.g., "'member', 'admin'" when admin implies member.
SatisfyingRelationsList string
// SourceRelationList is a SQL-formatted list of relations to search for userset grant tuples.
// For direct userset patterns, this is the same as the parent's RelationList.
// For closure userset patterns (inherited from implied relations), this is the source relation.
// e.g., "'viewer'" for a pattern inherited from viewer: [group#member]
SourceRelationList string
// SourceRelation is the relation where this userset pattern is defined (unquoted).
// Used for closure patterns to verify permission via check_permission_internal.
SourceRelation string
// IsClosurePattern is true if this pattern is inherited from an implied relation.
// When true, candidates need to be verified via check_permission_internal on the
// source relation to apply any exclusions or complex features.
IsClosurePattern bool
// HasWildcard is true if any satisfying relation allows wildcards.
// When true, membership check includes subject_id = '*'.
HasWildcard bool
// IsComplex is true if this pattern requires check_permission_internal for membership.
// This happens when any relation in the closure has TTU, exclusion, or intersection.
IsComplex bool
// IsSelfReferential is true if SubjectType == ObjectType and SubjectRelation == Relation.
// Self-referential usersets (e.g., group.member: [group#member]) require recursive CTEs.
// Non-self-referential usersets use JOIN-based expansion.
IsSelfReferential bool
}
ListUsersetPatternData contains data for rendering userset pattern expansion in list templates. For a pattern like [group#member], this generates a UNION block that: - Finds grant tuples where subject is group#member - JOINs with membership tuples to find subjects who are members
type NoUserset ¶
type NoUserset struct {
Source Expr
}
NoUserset checks if an expression does NOT contain a userset marker (#). Returns true if the expression does not contain '#'. Uses: position('#' in source) = 0
type NotExists ¶
type NotExists struct {
Query interface{ SQL() string }
}
NotExists represents a NOT EXISTS subquery.
type ObjectRef ¶
ObjectRef represents an object reference (type + id). An object is always identified by a type and an id.
func LiteralObject ¶
LiteralObject creates an ObjectRef with literal type and expression ID.
type OrExpr ¶
type OrExpr struct {
Exprs []Expr
}
OrExpr represents a logical OR of multiple expressions.
type Param ¶
type Param string
Param represents a function parameter (e.g., p_subject_type, p_object_id).
type ParentRelationCheck ¶
type ParentRelationCheck = schema.ParentRelationCheck
Type aliases for schema types used in analysis. This avoids prefixing every usage with "schema." throughout this file.
type ParentRelationData ¶
type ParentRelationData struct {
LinkingRelation string // Relation linking to parent object (e.g., "parent" in "viewer from parent")
ParentRelation string // Relation to verify on parent (e.g., "viewer" in "viewer from parent")
AllowedLinkingTypes string // SQL-formatted list of allowed parent types (e.g., "'folder', 'org'")
}
ParentRelationData contains data for rendering recursive access checks.
type ParentRelationInfo ¶
type ParentRelationInfo struct {
Relation string // Relation to check on parent (e.g., "viewer")
LinkingRelation string // Relation that links to parent (e.g., "parent")
AllowedLinkingTypes []string // Types allowed for linking relation (e.g., ["folder", "org"])
}
ParentRelationInfo represents a "X from Y" pattern (tuple-to-userset). For "viewer from parent" on a folder type, this would have:
- Relation: "viewer" (the relation to check on the parent)
- LinkingRelation: "parent" (the relation that links to the parent)
- AllowedLinkingTypes: ["folder"] (types the linking relation can point to)
The actual parent type is determined at runtime from the linking relation's subject types. AllowedLinkingTypes captures these for code generation.
type RelationAnalysis ¶
type RelationAnalysis struct {
ObjectType string // The object type (e.g., "document")
Relation string // The relation name (e.g., "viewer")
Features RelationFeatures // Feature flags determining what SQL to generate
// CanGenerate is true if this relation can use generated SQL for check.
// This is computed by checking both the relation's own features AND
// ensuring all relations in the satisfying closure are simply resolvable.
// Set by ComputeCanGenerate after all relations are analyzed.
CanGenerate bool
// CannotGenerateReason explains why CanGenerate is false.
// Empty when CanGenerate is true.
CannotGenerateReason string
// CanGenerateListValue is true if this relation can use generated SQL for list functions.
// List functions have stricter requirements than check functions because they use
// set operations (UNION, EXCEPT) rather than boolean composition.
// Set by ComputeCanGenerate after all relations are analyzed.
CanGenerateListValue bool
// CannotGenerateListReason explains why CanGenerateListValue is false.
// Empty when CanGenerateListValue is true.
CannotGenerateListReason string
// For Direct/Implied patterns - from closure table
SatisfyingRelations []string // Relations that satisfy this one (e.g., ["viewer", "editor", "owner"])
// For Exclusion patterns
ExcludedRelations []string // Relations to exclude (for simple "but not X" patterns)
// SimpleExcludedRelations are excluded relations that can be checked with
// a direct tuple lookup (no userset, TTU, exclusion, or intersection).
SimpleExcludedRelations []string
// ComplexExcludedRelations are excluded relations that need function calls
// (have userset, TTU, exclusion, intersection, or implied closure).
// The generated code will call check_permission_internal for these.
ComplexExcludedRelations []string
// ExcludedParentRelations captures "but not X from Y" patterns (TTU exclusions).
// These are resolved by looking up the linking relation Y and calling
// check_permission_internal for relation X on each linked object.
ExcludedParentRelations []ParentRelationInfo
// ExcludedIntersectionGroups captures "but not (A and B)" patterns.
// These are resolved by ANDing together check_permission_internal calls
// for each relation in the group.
ExcludedIntersectionGroups []IntersectionGroupInfo
// For Userset patterns
UsersetPatterns []UsersetPattern // [group#member] patterns
// For Recursive patterns (tuple-to-userset)
ParentRelations []ParentRelationInfo
// For Intersection patterns
IntersectionGroups []IntersectionGroupInfo
// HasComplexUsersetPatterns is true if any userset pattern is complex
// (requires check_permission_internal call). When true, the generated
// function needs PL/pgSQL with cycle detection.
HasComplexUsersetPatterns bool
// Direct subject types (for generating direct tuple checks)
DirectSubjectTypes []string // e.g., ["user", "org"]
// AllowedSubjectTypes is the union of all subject types from satisfying relations.
// This is used to enforce type restrictions in generated SQL.
// Computed by ComputeCanGenerate.
AllowedSubjectTypes []string
// SimpleClosureRelations contains relations in the closure that can use tuple lookup.
// These are relations without exclusions, usersets, recursion, or intersections.
// Computed by ComputeCanGenerate.
SimpleClosureRelations []string
// ComplexClosureRelations contains relations in the closure that need function calls.
// These are relations with exclusions that are themselves generatable.
// Computed by ComputeCanGenerate.
ComplexClosureRelations []string
// IntersectionClosureRelations contains relations in the closure that have intersection
// patterns and are list-generatable. These need to be handled by composing with their
// list function rather than tuple lookup (since intersection relations have no tuples).
// Computed by computeCanGenerateList.
IntersectionClosureRelations []string
// ClosureUsersetPatterns contains userset patterns from closure relations.
// For example, if `can_view: viewer` and `viewer: [group#member]`, then
// can_view's ClosureUsersetPatterns includes group#member.
// This is used by list functions to expand usersets from implied relations.
// Computed by ComputeCanGenerate.
ClosureUsersetPatterns []UsersetPattern
// ClosureParentRelations contains TTU patterns from closure relations.
// For example, if `can_view: viewer` and `viewer: viewer from parent`, then
// can_view's ClosureParentRelations includes the parent relation info.
// This is used by list functions to traverse TTU paths from implied relations.
// Computed by ComputeCanGenerate.
ClosureParentRelations []ParentRelationInfo
// ClosureExcludedRelations contains excluded relations from closure relations.
// For example, if `can_read: reader` and `reader: [user] but not restricted`,
// then can_read's ClosureExcludedRelations includes "restricted".
// This ensures exclusions are applied when accessing through implied relations.
// Computed by ComputeCanGenerate.
ClosureExcludedRelations []string
// IndirectAnchor describes how to reach a relation with direct grants when this
// relation has no direct/implied access paths. For pure TTU or pure userset patterns,
// this traces through the pattern to find an anchor relation with [user] or similar.
// Nil if the relation has direct/implied access or if no anchor can be found.
// Computed by ComputeCanGenerate via findIndirectAnchor.
IndirectAnchor *IndirectAnchorInfo
// MaxUsersetDepth is the maximum userset chain depth reachable from this relation.
// -1 means infinite (self-referential userset cycle), 0 means no userset patterns.
// Values >= 25 indicate the relation will always exceed the depth limit.
// Computed by ComputeCanGenerate via computeMaxUsersetDepth.
MaxUsersetDepth int
// ExceedsDepthLimit is true if MaxUsersetDepth >= 25.
// These relations generate functions that immediately raise M2002.
ExceedsDepthLimit bool
// SelfReferentialUsersets lists userset patterns that reference the same type and relation.
// e.g., for group.member: [user, group#member], this would contain
// UsersetPattern{SubjectType: "group", SubjectRelation: "member"}
// These patterns require recursive CTEs to expand nested group membership.
// Computed by ComputeCanGenerate.
SelfReferentialUsersets []UsersetPattern
// HasSelfReferentialUserset is true if len(SelfReferentialUsersets) > 0.
// When true, the list templates use recursive CTEs to expand the userset chain.
HasSelfReferentialUserset bool
}
RelationAnalysis contains all data needed to generate SQL for a relation. This struct is populated by AnalyzeRelations and consumed by the SQL generator.
func AnalyzeRelations ¶
func AnalyzeRelations(types []TypeDefinition, closure []ClosureRow) []RelationAnalysis
AnalyzeRelations classifies all relations and gathers data needed for SQL generation. It uses the precomputed closure to determine satisfying relations for each relation.
func ComputeCanGenerate ¶
func ComputeCanGenerate(analyses []RelationAnalysis) []RelationAnalysis
ComputeCanGenerate walks the dependency graph and sets CanGenerate on each analysis. A relation can be generated if: 1. Its own features allow generation (CanGenerate() on features returns true) 2. ALL relations in its satisfying closure are either:
- Simply resolvable (can use tuple lookup), OR
- Complex but generatable (have exclusions but can generate their own function)
3. If the relation has exclusions, excluded relations are classified as:
- Simple: can use direct tuple lookup (simply resolvable AND no implied closure)
- Complex: use check_permission_internal call (has TTU, userset, intersection, etc.)
For relations in the closure that need function calls (have exclusions but are generatable), the generated SQL will call their specialized check function rather than using tuple lookup.
This function also: - Propagates HasWildcard: if ANY relation in the closure supports wildcards - Collects AllowedSubjectTypes: union of all subject types from satisfying relations - Partitions closure relations into SimpleClosureRelations and ComplexClosureRelations - Partitions excluded relations into SimpleExcludedRelations and ComplexExcludedRelations
func (*RelationAnalysis) CanGenerateList ¶
func (a *RelationAnalysis) CanGenerateList() bool
CanGenerateList returns true if we can generate specialized list SQL for this relation. This returns the CanGenerateListValue field which is computed by ComputeCanGenerate.
List functions have different constraints than check functions because they use set operations (UNION, EXCEPT, INTERSECT) rather than boolean composition.
This is progressively relaxed as more list codegen patterns are implemented: - Phase 2: Direct/Implied patterns (simple tuple lookup with closure) - Phase 3+: Will add Exclusion support - Phase 4+: Will add Userset support - Phase 5+: Will add Recursive/TTU support
When CanGenerateList returns false, the list dispatcher falls through to the generic list functions (list_accessible_objects, list_accessible_subjects).
type RelationDefinition ¶
type RelationDefinition = schema.RelationDefinition
Type aliases for schema types used in analysis. This avoids prefixing every usage with "schema." throughout this file.
type RelationFeatures ¶
type RelationFeatures struct {
HasDirect bool // [user] - direct tuple lookup, the relation accepts specific subject types
HasImplied bool // viewer: editor - this relation is satisfied by another relation via closure
HasWildcard bool // [user:*] - allows wildcard grants that match any subject_id
HasUserset bool // [group#member] - grants via membership in another object's relation
HasRecursive bool // viewer from parent - grants inherited through parent/child relationships (TTU)
HasExclusion bool // but not blocked - denies access based on negative conditions
HasIntersection bool // writer and editor - requires AND of all parts
}
RelationFeatures tracks which features a relation uses. Multiple features can be present and will be composed in generated SQL. For example, a relation with HasDirect, HasUserset, and HasRecursive will generate SQL that ORs together all three access paths.
func (RelationFeatures) CanGenerate ¶
func (f RelationFeatures) CanGenerate() bool
CanGenerate returns true if we can generate specialized SQL for this feature set. This checks the features themselves - for full generation eligibility, also check that all dependency relations are generatable via RelationAnalysis.CanGenerate.
func (RelationFeatures) IsClosureCompatible ¶
func (f RelationFeatures) IsClosureCompatible() bool
IsClosureCompatible returns true if this relation can participate in a closure-based tuple lookup (relation IN ('a', 'b', 'c')) WITHOUT additional permission logic.
This returns false for relations with exclusions because when a relation A implies relation B (e.g., can_view: viewer), and B has an exclusion (e.g., viewer: [user] but not blocked), checking A requires applying B's exclusion. A simple closure lookup won't do this.
Note: A relation can still generate code for ITSELF even if it has an exclusion - its generated function handles the exclusion. But it can't be part of another relation's closure lookup.
func (RelationFeatures) IsSimplyResolvable ¶
func (f RelationFeatures) IsSimplyResolvable() bool
IsSimplyResolvable returns true if this relation can be fully resolved with a simple tuple lookup (no userset JOINs, recursion, exclusions, etc.). This is used to check if excluded relations can be handled with a simple EXISTS check, since exclusions on excluded relations would require full permission resolution.
func (RelationFeatures) NeedsCycleDetection ¶
func (f RelationFeatures) NeedsCycleDetection() bool
NeedsCycleDetection returns true if the generated function needs cycle detection.
func (RelationFeatures) NeedsPLpgSQL ¶
func (f RelationFeatures) NeedsPLpgSQL() bool
NeedsPLpgSQL returns true if the generated function needs PL/pgSQL (vs pure SQL). Required for cycle detection (variables, IF statements).
func (RelationFeatures) String ¶
func (f RelationFeatures) String() string
String returns a human-readable description of the features.
type SelectStmt ¶
type SelectStmt struct {
Distinct bool
Columns []string // Deprecated: use ColumnExprs instead
ColumnExprs []Expr // Preferred: typed column expressions
From string // Deprecated: use FromExpr instead
FromExpr TableExpr // Preferred: typed table expression
Alias string // Deprecated: use FromExpr's alias instead
Joins []JoinClause
Where Expr
Limit int
}
SelectStmt represents a SELECT query.
func (SelectStmt) NotExists ¶
func (s SelectStmt) NotExists() string
NotExists wraps a query in NOT EXISTS(...).
type SubjectRef ¶
SubjectRef represents a subject reference (type + id). A subject is always identified by a type and an id.
func SubjectParams ¶
func SubjectParams() SubjectRef
SubjectParams creates a SubjectRef from the standard function parameters.
type SubjectTypeRef ¶
type SubjectTypeRef = schema.SubjectTypeRef
Type aliases for schema types used in analysis. This avoids prefixing every usage with "schema." throughout this file.
type SubstringUsersetRelation ¶
type SubstringUsersetRelation struct {
Source Expr
}
SubstringUsersetRelation extracts the relation using substring/position. This variant is used when the input might already contain a userset marker and we need to extract just the relation part. Uses: substring(source from position('#' in source) + 1)
func (SubstringUsersetRelation) SQL ¶
func (s SubstringUsersetRelation) SQL() string
SQL renders the substring expression.
type TTUExclusionCheckData ¶
type TTUExclusionCheckData struct {
ObjectType string
ExcludedRelation string // The relation to check on the parent (e.g., "viewer")
LinkingRelation string // The linking relation (e.g., "parent")
AllowedLinkingTypes string // SQL-formatted list of allowed parent types (e.g., "'folder', 'org'")
InternalCheckFunctionName string
}
TTUExclusionCheckData contains data for rendering TTU exclusion checks. These check "but not X from Y" patterns by looking up the linking relation and calling check_permission_internal for each linked object.
type TableExpr ¶
type TableExpr interface {
// TableSQL returns the SQL for use in FROM/JOIN clauses.
TableSQL() string
// TableAlias returns the alias if any (empty string if none).
TableAlias() string
}
TableExpr is the interface for table expressions in FROM and JOIN clauses. Types that can be used as table sources implement this interface.
type TableRef ¶
TableRef wraps a raw table name for use as a TableExpr.
type TupleQuery ¶
type TupleQuery struct {
// contains filtered or unexported fields
}
TupleQuery is a fluent builder for queries against melange_tuples.
func Tuples ¶
func Tuples(alias string) *TupleQuery
Tuples creates a new TupleQuery with the given table alias.
func (*TupleQuery) Alias ¶
func (q *TupleQuery) Alias() string
Alias returns the query's table alias.
func (*TupleQuery) Build ¶
func (q *TupleQuery) Build() SelectStmt
Build returns the declarative SelectStmt for inspection or testing.
func (*TupleQuery) Col ¶
func (q *TupleQuery) Col(name string) Col
Col returns a column reference for this query's table (public API).
func (*TupleQuery) Distinct ¶
func (q *TupleQuery) Distinct() *TupleQuery
Distinct enables DISTINCT in the SELECT.
func (*TupleQuery) ExistsSQL ¶
func (q *TupleQuery) ExistsSQL() string
ExistsSQL returns the query wrapped in EXISTS(...).
func (*TupleQuery) InnerJoin ¶
func (q *TupleQuery) InnerJoin(table, alias string, on ...Expr) *TupleQuery
InnerJoin adds an INNER JOIN clause.
func (*TupleQuery) JoinClosure ¶
func (q *TupleQuery) JoinClosure(alias, closureValues string, on ...Expr) *TupleQuery
JoinClosure adds an INNER JOIN to an inline VALUES closure table. closureValues should be in the format "('type1','rel1','sat1'),('type2','rel2','sat2')"
func (*TupleQuery) JoinRaw ¶
func (q *TupleQuery) JoinRaw(joinType, tableExpr string, on ...Expr) *TupleQuery
JoinRaw adds a JOIN with a raw table expression.
func (*TupleQuery) JoinTuples ¶
func (q *TupleQuery) JoinTuples(alias string, on ...Expr) *TupleQuery
JoinTuples adds an INNER JOIN to melange_tuples with the given alias.
func (*TupleQuery) LeftJoin ¶
func (q *TupleQuery) LeftJoin(table, alias string, on ...Expr) *TupleQuery
LeftJoin adds a LEFT JOIN clause.
func (*TupleQuery) Limit ¶
func (q *TupleQuery) Limit(n int) *TupleQuery
Limit sets the LIMIT clause.
func (*TupleQuery) NotExistsSQL ¶
func (q *TupleQuery) NotExistsSQL() string
NotExistsSQL returns the query wrapped in NOT EXISTS(...).
func (*TupleQuery) ObjectType ¶
func (q *TupleQuery) ObjectType(t string) *TupleQuery
ObjectType sets the object_type filter.
func (*TupleQuery) Relations ¶
func (q *TupleQuery) Relations(rels ...string) *TupleQuery
Relations sets the relation filter (IN clause).
func (*TupleQuery) Select ¶
func (q *TupleQuery) Select(cols ...string) *TupleQuery
Select sets the columns to select.
func (*TupleQuery) SelectCol ¶
func (q *TupleQuery) SelectCol(columns ...string) *TupleQuery
SelectCol adds a column with automatic table prefix.
func (*TupleQuery) SelectExpr ¶
func (q *TupleQuery) SelectExpr(exprs ...Expr) *TupleQuery
SelectExpr adds typed expressions as columns.
func (*TupleQuery) Where ¶
func (q *TupleQuery) Where(exprs ...Expr) *TupleQuery
Where adds arbitrary WHERE conditions.
func (*TupleQuery) WhereHasUserset ¶
func (q *TupleQuery) WhereHasUserset() *TupleQuery
WhereHasUserset adds a condition requiring subject_id to contain '#'.
func (*TupleQuery) WhereNoUserset ¶
func (q *TupleQuery) WhereNoUserset() *TupleQuery
WhereNoUserset adds a condition requiring subject_id to NOT contain '#'.
func (*TupleQuery) WhereObject ¶
func (q *TupleQuery) WhereObject(ref ObjectRef) *TupleQuery
WhereObject adds conditions for matching object type and ID.
func (*TupleQuery) WhereObjectID ¶
func (q *TupleQuery) WhereObjectID(id Expr) *TupleQuery
WhereObjectID adds a condition for object_id equals.
func (*TupleQuery) WhereSubject ¶
func (q *TupleQuery) WhereSubject(ref SubjectRef) *TupleQuery
WhereSubject adds conditions for matching subject type and ID.
func (*TupleQuery) WhereSubjectID ¶
func (q *TupleQuery) WhereSubjectID(id Expr, allowWildcard bool) *TupleQuery
WhereSubjectID adds a condition for subject_id matching. If allowWildcard is true, also matches "*".
func (*TupleQuery) WhereSubjectType ¶
func (q *TupleQuery) WhereSubjectType(t Expr) *TupleQuery
WhereSubjectType adds a condition for subject_type equals.
func (*TupleQuery) WhereSubjectTypeIn ¶
func (q *TupleQuery) WhereSubjectTypeIn(types ...string) *TupleQuery
WhereSubjectTypeIn adds a condition for subject_type IN.
func (*TupleQuery) WhereUsersetRelation ¶
func (q *TupleQuery) WhereUsersetRelation(rel string) *TupleQuery
WhereUsersetRelation adds a condition for the userset relation part.
type TypeDefinition ¶
type TypeDefinition = schema.TypeDefinition
Type aliases for schema types used in analysis. This avoids prefixing every usage with "schema." throughout this file.
type UsersetCheckData ¶
type UsersetCheckData struct {
ObjectType string
Relation string
SubjectType string
SubjectRelation string
// SatisfyingRelationsList is a SQL-formatted list of relations that satisfy SubjectRelation.
// For example: "'member_c4', 'member_c3', 'member_c2', 'member_c1', 'member'"
SatisfyingRelationsList string
// HasWildcard is true if the subject relation supports wildcards.
// When true, the membership check should also match subject_id = '*'.
HasWildcard bool
InternalCheckFunctionName string
}
UsersetCheckData contains data for rendering userset check template.
type UsersetCheckInput ¶
type UsersetObjectID ¶
type UsersetObjectID struct {
Source Expr
}
UsersetObjectID extracts the object ID from a userset identifier. "group:1#member" -> "group:1" Uses: split_part(source, '#', 1)
func (UsersetObjectID) SQL ¶
func (u UsersetObjectID) SQL() string
SQL renders the split_part expression.
type UsersetPattern ¶
type UsersetPattern struct {
SubjectType string // e.g., "group"
SubjectRelation string // e.g., "member"
// SatisfyingRelations contains all relations in the closure of SubjectRelation.
// For example, if SubjectRelation="member_c4" and member_c4 is implied by member,
// this would contain ["member_c4", "member_c3", "member_c2", "member_c1", "member"].
// This is populated by ComputeCanGenerate from the closure data.
SatisfyingRelations []string
// SourceRelation is the relation where this userset pattern is defined.
// For direct patterns, this is the same as the relation being analyzed.
// For closure patterns (inherited from implied relations), this is the source relation.
// e.g., for can_view: viewer where viewer: [group#member], SourceRelation="viewer"
// This is used by list functions to search for grant tuples in the correct relation.
SourceRelation string
// HasWildcard is true if any relation in the closure supports wildcards.
// When true, the userset check should match membership tuples with subject_id = '*'.
// This is populated by ComputeCanGenerate from the subject relation's features.
HasWildcard bool
// IsComplex is true if any relation in the closure is not closure-compatible
// (has userset, TTU, exclusion, or intersection). When true, the userset check
// must call check_permission_internal to verify membership instead of using
// a simple tuple JOIN.
// This is populated by ComputeCanGenerate.
IsComplex bool
}
UsersetPattern represents a [type#relation] pattern in a relation definition. For example, [group#member] would have SubjectType="group" and SubjectRelation="member".
type UsersetRelation ¶
type UsersetRelation struct {
Source Expr
}
UsersetRelation extracts the relation from a userset identifier. "group:1#member" -> "member" Uses: split_part(source, '#', 2)
func (UsersetRelation) SQL ¶
func (u UsersetRelation) SQL() string
SQL renders the split_part expression.
type ValuesTable ¶
type ValuesTable struct {
Values string // The VALUES content (e.g., "('a', 'b'), ('c', 'd')")
Alias string // Table alias (e.g., "c")
Columns []string // Column names (e.g., ["object_type", "relation"])
}
ValuesTable represents a VALUES clause as a table expression. Used to inline data like closure values without database tables.
Example: ValuesTable{Values: "('doc', 'viewer', 'editor')", Alias: "c", Columns: []string{"object_type", "relation", "satisfying_relation"}} Renders: (VALUES ('doc', 'viewer', 'editor')) AS c(object_type, relation, satisfying_relation)
func ClosureValuesTable ¶
func ClosureValuesTable(values, alias string) ValuesTable
ClosureValuesTable creates a standard closure VALUES table. The table has columns: object_type, relation, satisfying_relation
func UsersetValuesTable ¶
func UsersetValuesTable(values, alias string) ValuesTable
UsersetValuesTable creates a standard userset VALUES table. The table has columns: object_type, relation, subject_type, subject_relation
func (ValuesTable) TableAlias ¶
func (v ValuesTable) TableAlias() string
TableAlias implements TableExpr.