Documentation
¶
Overview ¶
Package sqlgen generates specialized SQL functions for OpenFGA authorization checks.
Overview ¶
This package is the SQL code generator for Melange, an OpenFGA-to-PostgreSQL compiler. It analyzes OpenFGA authorization models and generates optimized PostgreSQL functions that evaluate permission checks and list operations without recursive graph traversal.
Architecture ¶
The generator operates in three phases:
- Analysis: Parse relation features (direct, implied, userset, TTU, exclusion, intersection)
- Planning: Determine which SQL patterns to use based on feature combinations
- Rendering: Generate PL/pgSQL functions using the SQL DSL
Subpackages ¶
The package is organized into focused subpackages:
- analysis: Relation analysis and feature detection
- sqldsl: Type-safe SQL DSL for building queries
- plpgsql: PL/pgSQL function builders
- tuples: Tuple table query builders
- inline: Inline data generation for closure and userset tables
SQL DSL ¶
The sqldsl subpackage provides a domain-specific DSL for constructing PostgreSQL queries. Rather than building SQL strings directly, code uses typed DSL elements that compose together and render to SQL.
Example - Direct tuple check:
query := Tuples("t").
ObjectType("document").
Relations("viewer").
WhereSubjectType(SubjectType).
WhereSubjectID(SubjectID, true). // true = allow wildcards
Select("1").
Limit(1)
exists := Exists{Query: query.Build()}
sql := exists.SQL() // Renders to PostgreSQL
Generated Functions ¶
For each relation in the schema, the generator produces:
- Specialized check function: check_{type}_{relation}(subject_type, subject_id, object_id)
- No-wildcard variant: check_{type}_{relation}_nw(...)
- List objects function: list_{type}_{relation}_obj(subject_type, subject_id, limit, cursor)
- List subjects function: list_{type}_{relation}_sub(object_id, subject_type, limit, cursor)
The generator also produces dispatcher functions (check_permission, list_accessible_objects, list_accessible_subjects) that route requests to the appropriate specialized function based on object type and relation name.
Relation Patterns ¶
The generator handles all OpenFGA relation patterns:
- Direct: [user] - direct tuple lookup
- Implied: viewer: editor - closure-based lookup
- Wildcard: [user:*] - match any subject_id
- Userset: [group#member] - JOIN to expand group membership
- TTU (Tuple-to-userset): viewer from parent - check permission on linked objects
- Exclusion: but not blocked - anti-join or function call to deny access
- Intersection: writer and editor - INTERSECT of multiple checks
Code Generation Flow ¶
The typical flow for generating SQL:
- Call AnalyzeRelations to classify all relations and detect features
- Call ComputeCanGenerate to determine generation eligibility and populate metadata
- Call GenerateSQL to produce all function definitions
- Apply the generated SQL during migration
Implementation Notes ¶
The DSL uses the builder pattern with method chaining for fluent composition. All DSL types implement either Expr (for expressions) or SQLer (for statements). The SQL() method renders the final PostgreSQL syntax.
Cycle detection for recursive patterns uses a visited array passed through check_permission_internal calls. Depth limits prevent unbounded recursion.
For relations with complex patterns (deep TTU chains, exclusions on excluded relations), the generator delegates to check_permission_internal which handles the full authorization semantics including fallback to the generic implementation.
Index ¶
- Constants
- Variables
- func BuildCycleNode(keyExpr string) string
- func BuildEvidenceJSON(alias string) string
- func BuildExpandComputedLeafJSON(usersetExpr string) string
- func BuildExpandDifferenceJSON(baseExpr, subtractExpr string) string
- func BuildExpandIntersectionJSON(childExprs []string) string
- func BuildExpandNodeJSON(nameExpr, valueExpr string) string
- func BuildExpandNodeName(typeExpr, idExpr, relation string) string
- func BuildExpandTTULeafJSON(tuplesetExpr string, computedExprs []string) string
- func BuildExpandTreeRoot(rootExpr string) string
- func BuildExpandUnionJSON(childExprs []string) string
- func BuildExpandUsersLeafJSON(usersExpr, usersTruncatedExpr string) string
- func BuildNodeJSON(nodeType TraceNodeType, a NodeJSONArgs) string
- func BuildObjectIdentExpr(typeExpr, idExpr string) string
- func BuildSubjectRefJSON(typeExpr, idExpr string) string
- func BuildTruncatedNode() string
- func CollectFunctionNames(analyses []RelationAnalysis) []string
- func ComputeExpandEligibility(analyses []RelationAnalysis) map[string]map[string]bool
- func ComputeExplainEligibility(analyses []RelationAnalysis) map[string]map[string]bool
- func RenderCheckFunction(plan CheckPlan, blocks CheckBlocks) (string, error)
- func RenderExpandFunction(plan ExpandPlan) string
- func RenderExplainFunction(plan CheckPlan, blocks CheckBlocks) (string, error)
- func RenderListObjectsComposedFunction(plan ListPlan, blocks ComposedObjectsBlockSet) (string, error)
- func RenderListObjectsDepthExceededFunction(plan ListPlan) string
- func RenderListObjectsFunction(plan ListPlan, blocks BlockSet) (string, error)
- func RenderListObjectsRecursiveFunction(plan ListPlan, blocks RecursiveBlockSet) (string, error)
- func RenderListObjectsSelfRefUsersetFunction(plan ListPlan, blocks SelfRefUsersetBlockSet) (string, error)
- func RenderListSubjectsComposedFunction(plan ListPlan, blocks ComposedSubjectsBlockSet) (string, error)
- func RenderListSubjectsDepthExceededFunction(plan ListPlan) string
- func RenderListSubjectsFunction(plan ListPlan, blocks BlockSet) (string, error)
- func RenderListSubjectsIntersectionFunction(plan ListPlan, blocks SubjectsIntersectionBlockSet) (string, error)
- func RenderListSubjectsRecursiveFunction(plan ListPlan, blocks SubjectsRecursiveBlockSet) (string, error)
- func RenderListSubjectsSelfRefUsersetFunction(plan ListPlan, blocks SelfRefUsersetSubjectsBlockSet) (string, error)
- type Add
- type Alias
- type AnchorPathStep
- type AndExpr
- type ArrayAppend
- type ArrayContains
- type ArrayLength
- type ArrayLiteral
- type Assign
- type BlockSet
- type Bool
- type CTEDef
- type CaseExpr
- type CaseWhen
- type Cast
- type CheckBlocks
- type CheckPermission
- type CheckPermissionCall
- type CheckPlan
- type ClosureRow
- type Col
- type Comment
- type CommentedSQL
- type ComposedObjectsBlockSet
- type ComposedSubjectsBlockSet
- type Concat
- type Decl
- type DispatcherCase
- type DispatcherData
- type EmptyArray
- type Eq
- type ExcludedIntersectionGroup
- type ExcludedIntersectionPart
- type ExcludedParentRelation
- type ExclusionConfig
- type Exists
- type ExpandExclusion
- type ExpandPlan
- type ExpandRewrite
- type Expr
- func CheckPermissionExpr(databaseSchema, functionName string, subject SubjectRef, relation string, ...) Expr
- func CheckPermissionInternalExpr(databaseSchema string, subject SubjectRef, relation string, object ObjectRef, ...) Expr
- func SimpleExclusion(databaseSchema, objectType, relation string, ...) Expr
- type ForLoop
- type Func
- type FuncArg
- type FuncCallEq
- type FuncCallNe
- type FunctionCallExpr
- type GenerateSQLOptions
- type GeneratedSQL
- type GenerationCapabilities
- type Gt
- type Gte
- type HasUserset
- type If
- type ImpliedFunctionCheck
- type In
- type InCTESelect
- type InFunctionSelect
- type IndexRecommendation
- type IndirectAnchorInfo
- type InlineSQLData
- type Int
- type IntersectSubquery
- type IntersectionGroup
- type IntersectionGroupCheck
- type IntersectionGroupInfo
- type IntersectionPart
- type IntersectionPartCheck
- type IsNotNull
- type IsNull
- type IsWildcard
- type JoinClause
- type LateralFunction
- type Like
- type ListAnchorPathStepData
- type ListDispatcherCase
- type ListDispatcherData
- type ListGeneratedSQL
- type ListIndirectAnchorData
- type ListParentRelationData
- type ListPlan
- type ListStrategy
- type ListUsersetPatternData
- type Lit
- type Lt
- type Lte
- type NamedFunction
- type Ne
- type NoUserset
- type NodeJSONArgs
- type NotExists
- type NotExpr
- type NotIn
- type NotLike
- type Null
- type ObjectRef
- type OrExpr
- type Param
- type Paren
- type ParentRelationBlock
- type ParentRelationCheck
- type ParentRelationInfo
- type PlpgsqlFunction
- type Position
- type QueryBlock
- type Raise
- type Raw
- type RawStmt
- type RecursiveBlockSet
- type RelationAnalysis
- type RelationDefinition
- type RelationFeatures
- type Return
- type ReturnInt
- type ReturnQuery
- type ReturnValue
- type SQLer
- type SelectInto
- type SelectIntoVar
- type SelectStmt
- type SelfRefUsersetBlockSet
- type SelfRefUsersetSubjectsBlockSet
- type SqlFunction
- type Stmt
- type Sub
- type SubjectRef
- type SubjectTypeRef
- type SubjectsIntersectionBlockSet
- type SubjectsRecursiveBlockSet
- type Substring
- type TableExpr
- type TableRef
- type TraceNodeType
- type TupleNotIn
- type TupleQuery
- type TypeDefinition
- type TypedQueryBlock
- type TypedValuesTable
- type UnionAll
- type UnionSubquery
- type UsersetNormalized
- type UsersetObjectID
- type UsersetPattern
- type UsersetRelation
- type ValuesRow
- type ValuesTable
- type WithCTE
Constants ¶
const ( ListStrategyDirect = analysis.ListStrategyDirect ListStrategyUserset = analysis.ListStrategyUserset ListStrategyRecursive = analysis.ListStrategyRecursive ListStrategyIntersection = analysis.ListStrategyIntersection ListStrategyDepthExceeded = analysis.ListStrategyDepthExceeded ListStrategySelfRefUserset = analysis.ListStrategySelfRefUserset ListStrategyComposed = analysis.ListStrategyComposed )
Variables ¶
var ( SubjectType = sqldsl.SubjectType SubjectID = sqldsl.SubjectID ObjectType = sqldsl.ObjectType ObjectID = sqldsl.ObjectID Visited = sqldsl.Visited ParamRef = sqldsl.ParamRef LitText = sqldsl.LitText And = sqldsl.And Or = sqldsl.Or Not = sqldsl.Not ExistsExpr = sqldsl.ExistsExpr TableAs = sqldsl.TableAs TypedClosureValuesTable = sqldsl.TypedClosureValuesTable TypedUsersetValuesTable = sqldsl.TypedUsersetValuesTable ClosureTable = sqldsl.ClosureTable UsersetTable = sqldsl.UsersetTable Ident = sqldsl.Ident SafeIdentifier = sqldsl.SafeIdentifier RenderBlocks = sqldsl.RenderBlocks RenderUnionBlocks = sqldsl.RenderUnionBlocks IndentLines = sqldsl.IndentLines WrapWithPagination = sqldsl.WrapWithPagination WrapWithPaginationWildcardFirst = sqldsl.WrapWithPaginationWildcardFirst SubjectIDMatch = sqldsl.SubjectIDMatch NormalizedUsersetSubject = sqldsl.NormalizedUsersetSubject SelectAs = sqldsl.SelectAs SubjectParams = sqldsl.SubjectParams LiteralObject = sqldsl.LiteralObject Sqlf = sqldsl.Sqlf Optf = sqldsl.Optf InternalPermissionCheckCall = sqldsl.InternalPermissionCheckCall NoWildcardPermissionCheckCall = sqldsl.NoWildcardPermissionCheckCall WildcardPermissionCheckCall = sqldsl.WildcardPermissionCheckCall SpecializedCheckCall = sqldsl.SpecializedCheckCall InternalCheckCall = sqldsl.InternalCheckCall VisitedKey = sqldsl.VisitedKey VisitedWithKey = sqldsl.VisitedWithKey ListObjectsFunctionName = sqldsl.ListObjectsFunctionName ListSubjectsFunctionName = sqldsl.ListSubjectsFunctionName RecursiveCTE = sqldsl.RecursiveCTE SimpleCTE = sqldsl.SimpleCTE MultiCTE = sqldsl.MultiCTE MultiLineComment = sqldsl.MultiLineComment )
var ( ComputeRelationClosure = analysis.ComputeRelationClosure AnalyzeRelations = analysis.AnalyzeRelations ComputeCanGenerate = analysis.ComputeCanGenerate DetermineListStrategy = analysis.DetermineListStrategy BuildAnalysisLookup = analysis.BuildAnalysisLookup )
var ( ListObjectsArgs = plpgsql.ListObjectsArgs ListSubjectsArgs = plpgsql.ListSubjectsArgs ListObjectsReturns = plpgsql.ListObjectsReturns ListSubjectsReturns = plpgsql.ListSubjectsReturns ListObjectsFunctionHeader = plpgsql.ListObjectsFunctionHeader ListSubjectsFunctionHeader = plpgsql.ListSubjectsFunctionHeader ListObjectsDispatcherArgs = plpgsql.ListObjectsDispatcherArgs ListSubjectsDispatcherArgs = plpgsql.ListSubjectsDispatcherArgs )
var ( BuildInlineSQLData = inline.BuildInlineSQLData BuildClosureTypedRows = inline.BuildClosureTypedRows BuildUsersetTypedRows = inline.BuildUsersetTypedRows )
var Tuples = tuples.Tuples
Functions ¶
func BuildCycleNode ¶ added in v0.8.4
BuildCycleNode emits a NodeCycle with a label identifying the cycle key. keyExpr is the SQL expression for the visited-key string.
func BuildEvidenceJSON ¶ added in v0.8.4
BuildEvidenceJSON emits a SQL fragment that constructs a TupleRef JSONB from a tuple-row alias's columns. Use inside SELECT lists or as a jsonb_agg() input when collecting evidence for a node.
alias is the row alias (commonly "t" against `melange_tuples t`) and is required. Bare column references would clash with PL/pgSQL variables in scope and risk ambiguity in CTE and join contexts.
func BuildExpandComputedLeafJSON ¶ added in v0.8.4
BuildExpandComputedLeafJSON emits a `{leaf: {computed: {userset: ...}}}` fragment for a single computed-userset rewrite (e.g. `define viewer: editor` emits a leaf whose Computed.Userset is "<obj>:#editor"). usersetExpr is a SQL expression that evaluates to the canonical "<obj_type>:<obj_id>#<rel>" string; the helper handles all the jsonb_build_object wrapping including the outer `leaf` key so the caller can concatenate the result directly into a UsersetTreeNode.
func BuildExpandDifferenceJSON ¶ added in v0.8.4
BuildExpandDifferenceJSON wraps base/subtract Node JSONBs in `{difference: {base: ..., subtract: ...}}`. Both args are SQL expressions each evaluating to a complete UsersetTreeNode. Named slots (not positional children) match OpenFGA's proto exactly so consumers can address each half without ambiguity. Reserved for slice 2.2.
func BuildExpandIntersectionJSON ¶ added in v0.8.4
BuildExpandIntersectionJSON wraps child Node JSONBs in `{intersection: {nodes: [...]}}`. Same shape as the Union wrapper — the discriminator on the outer UsersetTreeNode is which key is populated. Reserved for slice 2.2.
func BuildExpandNodeJSON ¶ added in v0.8.4
BuildExpandNodeJSON wraps a name + one populated value slot into a complete UsersetTreeNode. valueExpr is a SQL expression that evaluates to one of the leaf/difference/union/intersection JSONB objects emitted by the Build*JSON helpers above — it must already include the discriminator key (the `users`/`computed`/`tuple_to_userset` wrapping for leaves, or the `union`/`intersection`/`difference` wrapping for non-leaves).
The resulting object always has `name` first so jsonb_build_object's deterministic key ordering aligns with the Go struct's field order.
func BuildExpandNodeName ¶ added in v0.8.4
BuildExpandNodeName emits the canonical "<type>:<id>#<relation>" name every UsersetTreeNode carries. typeExpr and idExpr are SQL expressions (column refs, literals); relation is a Go string that gets quoted into a SQL literal here.
func BuildExpandTTULeafJSON ¶ added in v0.8.4
BuildExpandTTULeafJSON emits a `{leaf: {tuple_to_userset: {...}}}` fragment for a TTU rewrite. tuplesetExpr names the linking relation (canonical "<obj_type>:<obj_id>#<linking>"); computedExprs are SQL expressions each evaluating to a Computed JSONB object (matches the proto's repeated computed field — one entry per allowed parent relation).
Reserved for slice 2.2 — declared now so the helper surface is complete and the per-rewrite renderer can call it once the analysis for TTU lands.
func BuildExpandTreeRoot ¶ added in v0.8.4
BuildExpandTreeRoot emits the top-level `{root: <node>}` envelope every Expand response uses. rootExpr is a SQL expression evaluating to a complete UsersetTreeNode JSONB. Companion to expandNoEntrySentinelSQL in expand_functions.go.
func BuildExpandUnionJSON ¶ added in v0.8.4
BuildExpandUnionJSON wraps per-rewrite child Node JSONB objects in `{union: {nodes: [...]}}`. childExprs are SQL expressions each evaluating to a complete UsersetTreeNode (i.e. each carries its own name + one populated leaf/difference/union/intersection slot).
func BuildExpandUsersLeafJSON ¶ added in v0.8.4
BuildExpandUsersLeafJSON emits a `{leaf: {users: {users: [...]}}}` fragment. usersExpr is a SQL expression evaluating to a JSONB array of OpenFGA-formatted user strings (e.g. the result of jsonb_agg() over a SELECT). When usersTruncatedExpr is non-empty it should evaluate to a boolean; the users_truncated key is omitted from the JSONB when the expression is false (matching the omitempty Go tag).
usersExpr must always be a JSONB array — never NULL. Callers that build the array via aggregation should COALESCE the SELECT to '[]'::jsonb so an empty leaf is structurally `{users: []}` rather than `{users: null}`.
func BuildNodeJSON ¶ added in v0.8.4
func BuildNodeJSON(nodeType TraceNodeType, a NodeJSONArgs) string
BuildNodeJSON emits a Node JSONB. Fields are emitted in the order they appear on melange.Node (type, label, evidence, children, users, result) so jsonb_build_object preserves a deterministic key order. Empty NodeJSONArgs fields are omitted from the output.
func BuildObjectIdentExpr ¶ added in v0.8.4
BuildObjectIdentExpr emits the canonical "<type>:<id>" concatenation shared by every trace envelope so the format never drifts.
func BuildSubjectRefJSON ¶ added in v0.8.4
BuildSubjectRefJSON emits a SubjectRef JSONB. typeExpr and idExpr are SQL expressions (column refs, literals, concatenations) for the type and id. Useful for Expand leaf enumeration.
func BuildTruncatedNode ¶ added in v0.8.4
func BuildTruncatedNode() string
BuildTruncatedNode emits the universal "subtree omitted, p_max_nodes hit" sentinel. No label is required — the truncation reason is implicit.
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 to identify stale functions that need to be dropped when the schema changes.
The returned list includes:
- Specialized check functions: check_{type}_{relation}
- No-wildcard check variants: check_{type}_{relation}_nw
- Specialized list functions: list_{type}_{relation}_obj, list_{type}_{relation}_sub
- Dispatcher functions (always included): check_permission, list_accessible_objects, etc.
func ComputeExpandEligibility ¶ added in v0.8.4
func ComputeExpandEligibility(analyses []RelationAnalysis) map[string]map[string]bool
ComputeExpandEligibility returns, for each (object_type, relation), whether the expand renderer can produce a tree for it. Mirrors ComputeExplainEligibility's surface so CollectFunctionNames can call either without branching. No transitive sweep is needed because Expand is shallow — computed/TTU rewrites surface as unresolved pointers, so an ineligible callee doesn't disable the caller.
func ComputeExplainEligibility ¶ added in v0.8.4
func ComputeExplainEligibility(analyses []RelationAnalysis) map[string]map[string]bool
ComputeExplainEligibility returns, for each (object_type, relation), whether the explain renderer can produce a trace that agrees with Check.
The result is the fixed point of the per-relation locality check (explainLocalSupported) plus the recursive-dependency downgrades enumerated in anyExplainDepIneligible.
Eligibility is monotonically non-increasing: each pass can downgrade a relation when a freshly-discovered ineligible dependency surfaces, but nothing ever flips back. The fixed point is reached when no relation changes in a pass. Cross-type TTU parents do not poison the wrapper — the dispatcher's no-entry sentinel covers missing per-iteration callees with a well-formed result=false trace.
Exported so callers that build a CollectFunctionNames input by hand can produce the same map GenerateSQL stashes on GeneratedSQL.ExplainEligible.
func RenderCheckFunction ¶
func RenderCheckFunction(plan CheckPlan, blocks CheckBlocks) (string, error)
func RenderExpandFunction ¶ added in v0.8.4
func RenderExpandFunction(plan ExpandPlan) string
RenderExpandFunction is the entry point for expand_* function generation. Returns the complete CREATE OR REPLACE FUNCTION text plus a trailing newline so file-level concatenation stays clean.
func RenderExplainFunction ¶ added in v0.8.4
func RenderExplainFunction(plan CheckPlan, blocks CheckBlocks) (string, error)
RenderExplainFunction is the entry point for explain_* function generation.
func RenderListObjectsComposedFunction ¶
func RenderListObjectsComposedFunction(plan ListPlan, blocks ComposedObjectsBlockSet) (string, error)
============================================================================= Composed Strategy Render Functions (List Objects) ============================================================================= RenderListObjectsComposedFunction renders a list_objects function for composed access. Composed functions handle indirect anchor patterns (TTU and userset composition).
func RenderListObjectsDepthExceededFunction ¶
RenderListObjectsDepthExceededFunction renders a list_objects function for a relation that exceeds the userset depth limit. The generated function raises M2002 immediately.
func RenderListObjectsFunction ¶
RenderListObjectsFunction renders a complete list_objects function from plan and blocks.
func RenderListObjectsRecursiveFunction ¶
func RenderListObjectsRecursiveFunction(plan ListPlan, blocks RecursiveBlockSet) (string, error)
RenderListObjectsRecursiveFunction renders a recursive list_objects function from plan and blocks. This handles TTU patterns with depth tracking and recursive CTEs.
func RenderListObjectsSelfRefUsersetFunction ¶
func RenderListObjectsSelfRefUsersetFunction(plan ListPlan, blocks SelfRefUsersetBlockSet) (string, error)
RenderListObjectsSelfRefUsersetFunction renders a list_objects function for self-referential userset patterns.
func RenderListSubjectsComposedFunction ¶
func RenderListSubjectsComposedFunction(plan ListPlan, blocks ComposedSubjectsBlockSet) (string, error)
RenderListSubjectsComposedFunction renders a list_subjects function for composed access.
func RenderListSubjectsDepthExceededFunction ¶
RenderListSubjectsDepthExceededFunction renders a list_subjects function for a relation that exceeds the userset depth limit. The generated function raises M2002 immediately.
func RenderListSubjectsFunction ¶
RenderListSubjectsFunction renders a complete list_subjects function from plan and blocks.
func RenderListSubjectsIntersectionFunction ¶
func RenderListSubjectsIntersectionFunction(plan ListPlan, blocks SubjectsIntersectionBlockSet) (string, error)
============================================================================= Intersection List Subjects Render Functions ============================================================================= RenderListSubjectsIntersectionFunction renders an intersection list_subjects function from plan and blocks. Intersection gathers candidates then filters with check_permission at the end.
func RenderListSubjectsRecursiveFunction ¶
func RenderListSubjectsRecursiveFunction(plan ListPlan, blocks SubjectsRecursiveBlockSet) (string, error)
============================================================================= Recursive List Subjects Render Functions ============================================================================= RenderListSubjectsRecursiveFunction renders a recursive list_subjects function from plan and blocks. This handles TTU patterns with subject_pool CTE and check_permission_internal calls.
func RenderListSubjectsSelfRefUsersetFunction ¶
func RenderListSubjectsSelfRefUsersetFunction(plan ListPlan, blocks SelfRefUsersetSubjectsBlockSet) (string, error)
============================================================================= Self-Referential Userset Render Functions (List Subjects) =============================================================================
Types ¶
type BlockSet ¶
type BlockSet struct {
Primary []TypedQueryBlock
Secondary []TypedQueryBlock
SecondarySelf *TypedQueryBlock
}
BlockSet contains query blocks for a list function.
func BuildListObjectsBlocks ¶
BuildListObjectsBlocks builds all query blocks for a list_objects function. Returns a BlockSet with Primary blocks for the main query path.
func BuildListSubjectsBlocks ¶
BuildListSubjectsBlocks builds all query blocks for a list_subjects function. Returns a BlockSet with Primary and optionally Secondary blocks.
type CheckBlocks ¶
type CheckBlocks struct {
DirectCheck Expr // EXISTS check for direct/implied tuple lookup
UsersetCheck Expr // EXISTS check for userset membership
ExclusionCheck Expr // EXISTS check for exclusion (denial)
UsersetSubjectSelfCheck SelectStmt // Validates when subject IS a userset
// UsersetSubjectComputedCheck validates userset subject via tuple join
UsersetSubjectComputedCheck SelectStmt
ParentRelationBlocks []ParentRelationBlock // TTU pattern checks
ImpliedFunctionCalls []ImpliedFunctionCheck // Complex implied relation checks
IntersectionGroups []IntersectionGroupCheck // AND groups for intersection patterns
HasStandaloneAccess bool // Access paths outside intersections exist
}
CheckBlocks contains all the DSL blocks needed to generate a check function.
func BuildCheckBlocks ¶
func BuildCheckBlocks(plan CheckPlan) (CheckBlocks, error)
BuildCheckBlocks builds all DSL blocks for a check function.
type CheckPermission ¶
type CheckPermission struct {
Schema string
Subject SubjectRef
Relation string
Object ObjectRef
Visited Expr // nil uses empty array
ExpectAllow bool // true compares "= 1", false compares "= 0"
}
CheckPermission represents a call to check_permission_internal.
func CheckAccess ¶
func CheckAccess(databaseSchema, relation, objectType string, objectID Expr) CheckPermission
CheckAccess creates a CheckPermission that expects access to be allowed.
func CheckNoAccess ¶
func CheckNoAccess(databaseSchema, relation, objectType string, objectID Expr) CheckPermission
CheckNoAccess creates a CheckPermission that expects access to be denied.
func (CheckPermission) SQL ¶
func (c CheckPermission) SQL() string
type CheckPermissionCall ¶
type CheckPermissionCall struct {
Schema string
FunctionName string
Subject SubjectRef
Relation string
Object ObjectRef
ExpectAllow bool
}
CheckPermissionCall represents a call to a specialized permission check function.
func (CheckPermissionCall) SQL ¶
func (c CheckPermissionCall) SQL() string
type CheckPlan ¶
type CheckPlan struct {
// Input data
Analysis RelationAnalysis
Inline InlineSQLData
DatabaseSchema string
// Function identity
FunctionName string
InternalCheckFunctionName string // Dispatcher function for recursive calls
ObjectType string
Relation string
FeaturesString string // Human-readable features for SQL comments
// Feature configuration
AllowWildcard bool // Whether wildcards are allowed
NoWildcard bool // True if this is a no-wildcard variant
Exclusions ExclusionConfig // Exclusion rules configuration
// Feature flags (derived from analysis)
HasDirect bool
HasImplied bool
HasUserset bool
HasExclusion bool
HasIntersection bool
HasRecursive bool
// Derived computation flags
HasStandaloneAccess bool // Has access paths outside of intersections
HasComplexUsersets bool // Has userset patterns requiring function calls
NeedsPLpgSQL bool // Requires PL/pgSQL (not pure SQL)
HasParentRelations bool // Has TTU patterns
HasImpliedFunctionCall bool // Has complex implied relations needing function calls
// Eligibility from unified analysis
Capabilities GenerationCapabilities
// Relation lists for closure lookups
RelationList []string // Relations for tuple lookup (self + simple closure)
ComplexClosure []string // Complex closure relations
AllowedSubjectTypes []string // Subject types allowed for this relation
// ComplexityByRelation maps (object_type, relation) to a cost score. Used to
// order ImpliedFunctionCalls (same object type) and ParentRelationBlocks
// (different object type) so cheapest callees are evaluated first in OR
// short-circuit chains. May be nil; absent entries score zero.
ComplexityByRelation map[string]map[string]int
// NeedsNoWildcard maps (object_type, relation) to whether a distinct _nw
// check function is emitted for it. Used when NoWildcard is true to decide,
// for each complex-closure callee, whether to call its _nw function or its
// base function (identical body, no _nw emitted). Nil means "always assume a
// _nw variant exists" (backward-compatible for direct plan-builder callers).
NeedsNoWildcard map[string]map[string]bool
}
CheckPlan contains all computed data needed to generate a check function. This separates plan computation from block building and rendering. Unlike CheckFunctionData, this contains no pre-rendered SQL fragments.
func BuildCheckPlan ¶
func BuildCheckPlan(a RelationAnalysis, inline InlineSQLData, databaseSchema string, noWildcard bool) CheckPlan
BuildCheckPlan creates a plan for generating a check function. Set noWildcard to true to generate a no-wildcard variant. Implied and parent function calls preserve declaration order; use BuildCheckPlanWithOrdering to provide a cost index that orders them cheap-first.
func BuildCheckPlanWithOrdering ¶ added in v0.8.3
func BuildCheckPlanWithOrdering(a RelationAnalysis, inline InlineSQLData, databaseSchema string, noWildcard bool, complexityByRelation map[string]map[string]int) CheckPlan
BuildCheckPlanWithOrdering is the variant of BuildCheckPlan that accepts a cost index by (object_type, relation). The index orders implied and parent function calls cheap-first in OR/AND chains so the cheapest branch short-circuits the rest. Pass nil to preserve declaration order.
func (CheckPlan) DetermineCheckFunctionType ¶
DetermineCheckFunctionType returns which type of check function to generate. Returns one of: "direct", "intersection", "recursive", "recursive_intersection"
type ComposedObjectsBlockSet ¶
type ComposedObjectsBlockSet struct {
// SelfBlock is the self-candidate check block
SelfBlock *TypedQueryBlock
// MainBlocks are the composed query blocks (TTU and/or userset paths)
MainBlocks []TypedQueryBlock
// AllowedSubjectTypes for the type guard
AllowedSubjectTypes []string
// Anchor metadata for comments
AnchorType string
AnchorRelation string
FirstStepType string
}
ComposedObjectsBlockSet contains blocks for a composed list_objects function. Composed functions handle indirect anchor patterns (TTU and userset composition).
func BuildListObjectsComposedBlocks ¶
func BuildListObjectsComposedBlocks(plan ListPlan) (ComposedObjectsBlockSet, error)
BuildListObjectsComposedBlocks builds block set for composed list_objects function.
type ComposedSubjectsBlockSet ¶
type ComposedSubjectsBlockSet struct {
// SelfBlock is the self-candidate check block (for userset filter)
SelfBlock *TypedQueryBlock
// UsersetFilterBlocks are candidate blocks for userset filter path
UsersetFilterBlocks []TypedQueryBlock
// RegularBlocks are candidate blocks for regular path
RegularBlocks []TypedQueryBlock
// AllowedSubjectTypes for the type guard
AllowedSubjectTypes []string
// HasExclusions indicates if exclusion predicates are needed
HasExclusions bool
// Anchor metadata for comments
AnchorType string
AnchorRelation string
FirstStepType string
}
ComposedSubjectsBlockSet contains blocks for a composed list_subjects function.
func BuildListSubjectsComposedBlocks ¶
func BuildListSubjectsComposedBlocks(plan ListPlan) (ComposedSubjectsBlockSet, error)
BuildListSubjectsComposedBlocks builds block set for composed list_subjects function.
type DispatcherCase ¶
type DispatcherCase struct {
DatabaseSchema string
ObjectType string
Relation string
CheckFunctionName string
Inlineable bool // true if simple direct-assignment only (bulk dispatcher can inline EXISTS)
DirectSubjectTypes []string // subject types allowed for direct tuples (used in inline)
SatisfyingRelations []string // relations in closure that satisfy this one (used in inline userset check)
}
DispatcherCase represents a single CASE WHEN branch in the dispatcher. Each case routes a specific (object_type, relation) pair to its specialized function.
type DispatcherData ¶
type DispatcherData struct {
FunctionName string
HasSpecializedFunctions bool
Cases []DispatcherCase
}
DispatcherData contains data for rendering the dispatcher template.
type ExcludedIntersectionGroup ¶
type ExcludedIntersectionGroup struct {
Parts []ExcludedIntersectionPart
}
ExcludedIntersectionGroup represents a complete intersection exclusion like "but not (A and B)". Access is denied if ALL parts in the group are satisfied.
type ExcludedIntersectionPart ¶
type ExcludedIntersectionPart struct {
Relation string // The relation to check
ExcludedRelation string // Optional nested exclusion (e.g., "editor but not owner")
ParentRelation *ExcludedParentRelation // Optional TTU pattern in the intersection
}
ExcludedIntersectionPart represents one part of an intersection exclusion. For "but not (editor and owner)", there would be two parts: one for editor, one for owner.
type ExcludedParentRelation ¶
type ExcludedParentRelation struct {
Relation string // The relation to check on the parent (e.g., "viewer")
LinkingRelation string // The relation linking to the parent (e.g., "parent")
AllowedLinkingTypes []string // Object types the linking relation can point to
}
ExcludedParentRelation represents a TTU exclusion pattern like "but not viewer from parent". The exclusion is satisfied when a tuple exists linking the object to a parent object that grants the excluded relation.
type ExclusionConfig ¶
type ExclusionConfig struct {
DatabaseSchema string
ObjectType string // The object type being checked
ObjectIDExpr Expr // Expression for the object ID (typically a column or parameter)
SubjectTypeExpr Expr // Expression for the subject type
SubjectIDExpr Expr // Expression for the subject ID
// SimpleExcludedRelations can use direct tuple lookups (NOT EXISTS).
SimpleExcludedRelations []string
// ComplexExcludedRelations need check_permission_internal calls.
ComplexExcludedRelations []string
// ExcludedParentRelations represent TTU exclusions (e.g., "but not viewer from parent").
ExcludedParentRelations []ExcludedParentRelation
// ExcludedIntersection represents intersection exclusions (e.g., "but not (A and B)").
ExcludedIntersection []ExcludedIntersectionGroup
// Compose, when set, lets complex exclusions replace the per-candidate
// check_permission_internal(...) = 0 with a set-oriented anti-join against
// the excluded relation's list_objects function. It is only valid in a
// list_objects context — ObjectIDExpr must be the per-row candidate column
// and the subject exprs query-constant params — so only the list_objects
// plan builder sets it; check and list_subjects contexts leave it nil and
// keep the per-candidate check.
Compose *exclusionCompose
}
ExclusionConfig holds all exclusion rules for a query. It classifies exclusions by complexity and generates appropriate SQL predicates.
func (ExclusionConfig) BuildExclusionCTE ¶
func (c ExclusionConfig) BuildExclusionCTE() string
BuildExclusionCTE builds a CTE that materializes all excluded subjects. Used for CTE-based exclusion optimization to precompute exclusions once instead of checking via NOT EXISTS for each result row.
Returns a SELECT statement producing a single column: subject_id
Example for "but not restricted" where restricted: [user]:
SELECT subject_id FROM melange_tuples
WHERE object_type = 'document'
AND relation IN ('restricted')
AND object_id = p_object_id
Result is used in anti-join pattern:
LEFT JOIN excluded_subjects ON excluded_subjects.subject_id = candidate.subject_id
OR excluded_subjects.subject_id = '*'
WHERE excluded_subjects.subject_id IS NULL
func (ExclusionConfig) BuildPredicates ¶
func (c ExclusionConfig) BuildPredicates() []Expr
BuildPredicates converts exclusion rules into SQL predicates.
Simple exclusions become NOT EXISTS subqueries checking for direct tuples. Complex exclusions become check_permission_internal(...) = 0 calls. TTU exclusions check for linking tuples where the parent grants the excluded relation. Intersection exclusions become NOT (part1 AND part2 AND ...) expressions.
All predicates are returned as a slice that should be ANDed into the WHERE clause.
func (ExclusionConfig) CanUseCTEOptimization ¶
func (c ExclusionConfig) CanUseCTEOptimization() bool
CanUseCTEOptimization returns true if exclusions can use CTE-based optimization.
Eligible when:
- Has simple exclusions (direct tuple lookups)
- No complex exclusions (require check_permission calls)
- No TTU exclusions (require parent traversal)
- No intersection exclusions (require AND logic)
The optimization materializes excluded subjects once in a CTE, then uses a single LEFT JOIN...WHERE IS NULL instead of repeated NOT EXISTS predicates.
func (ExclusionConfig) HasExclusions ¶
func (c ExclusionConfig) HasExclusions() bool
HasExclusions returns true if any exclusion rules are configured.
type ExpandExclusion ¶ added in v0.8.4
type ExpandExclusion struct {
Relation string
ParentRelation *ParentRelationInfo
Intersection *IntersectionGroupInfo
}
ExpandExclusion is one subtrahend of a `but not` chain. Exactly one of Relation / ParentRelation / Intersection is set; the renderer dispatches on shape to emit the matching subtract leaf.
type ExpandPlan ¶ added in v0.8.4
type ExpandPlan struct {
DatabaseSchema string
ObjectType string
Relation string
Rewrites []ExpandRewrite
// Exclusions captures the relation's `but not` subtrahends. Each
// entry wraps into a `Difference{base, subtract}` node, with the
// previous tree as `base` — chained exclusions like
// `(writer but not editor) but not owner` produce left-nested
// Differences. Each entry's shape determines its subtract leaf:
//
// - Relation set → Leaf.Computed pointer
// (`but not X` — covers both simple and complex; Expand emits a
// pointer and the caller chases via ExpandRecursive)
// - ParentRelation set → Leaf.TupleToUserset
// (`but not X from Y` — same shape as the top-level TTU rewrite)
// - Intersection set → Nodes.Intersection
// (`but not (A and B)` — reuses the intersection rewrite shape)
//
// An empty slice means the relation has no exclusion (the renderer
// emits the rewrites-derived tree directly).
Exclusions []ExpandExclusion
}
ExpandPlan is the per-relation input to RenderExpandFunction. It's computed from a RelationAnalysis by BuildExpandPlan; the rendering stage is a pure function of the plan so it stays trivially testable.
Rewrites carry the per-rewrite shape (direct or computed) in the order they should appear under the Union node. A single-rewrite plan emits the leaf directly (no Union wrapper); multi-rewrite plans emit the Union envelope around per-rewrite children.
func BuildExpandPlan ¶ added in v0.8.4
func BuildExpandPlan(a RelationAnalysis, databaseSchema string) (ExpandPlan, bool)
BuildExpandPlan derives the per-rewrite plan from a RelationAnalysis. Returns (plan, true) when the relation is eligible; the dispatcher routes ineligible relations to the no-entry sentinel. A plan with no rewrites is ineligible — the caller returns the sentinel rather than an empty tree.
type ExpandRewrite ¶ added in v0.8.4
type ExpandRewrite struct {
// Direct is the subject-type whitelist for a direct rewrite. nil/empty
// means this rewrite is not a direct grant.
Direct []string
// Computed is the implied relation name on the same object type for
// a computed-userset rewrite. Empty means this rewrite is not a
// computed pointer.
Computed string
// TTU carries the "X from Y" rewrite info. nil means this rewrite is
// not a TTU. When set the renderer emits a Leaf.TupleToUserset with
// tupleset = "<obj>:#<linking>" and one Computed per linked object
// (enumerated at expand time via jsonb_agg over melange_tuples).
TTU *ParentRelationInfo
// Intersection lists the parts for `a and b [and …]` rewrites. nil
// means this rewrite is not an intersection. When set the renderer
// emits a Nodes intersection wrapping one child per part — each
// child's value slot dispatches by part shape (Computed pointer for
// plain-relation parts, Leaf.Users for IsThis, Leaf.TupleToUserset
// for ParentRelation, Difference for per-part exclusion). Same
// shallow-pointer treatment as the Computed rewrite because
// OpenFGA's Expand never resolves intersection parts recursively.
Intersection []IntersectionPart
}
ExpandRewrite is one of the per-rewrite shapes Expand can emit. Exactly one field is populated; the discriminator is which one is non-zero.
type Expr ¶
Expressions
func CheckPermissionExpr ¶
func CheckPermissionExpr(databaseSchema, functionName string, subject SubjectRef, relation string, object ObjectRef, expect bool) Expr
CheckPermissionExpr returns a typed expression for a check_permission call.
func CheckPermissionInternalExpr ¶
func CheckPermissionInternalExpr(databaseSchema string, subject SubjectRef, relation string, object ObjectRef, expect bool) Expr
CheckPermissionInternalExpr returns a typed expression for check_permission_internal.
func SimpleExclusion ¶
func SimpleExclusion(databaseSchema, objectType, relation string, objectID, subjectType, subjectID Expr) Expr
SimpleExclusion creates a NOT EXISTS exclusion for a simple "but not" rule. This checks for the absence of a tuple granting the excluded relation to the subject. Wildcards are handled: if a wildcard tuple exists for the excluded relation, access is denied.
type GenerateSQLOptions ¶ added in v0.8.3
type GenerateSQLOptions struct {
// EnableMaterializedCTEs forces "AS MATERIALIZED" on multi-referenced CTEs
// inside generated list functions. The default (false) lets PostgreSQL
// decide whether to inline or materialize each CTE — which on benchmarked
// production-scale workloads (100K+ tuples) outperformed forced
// materialization by ~10% on heavy list_objects queries.
//
// Set true when profiling shows a workload where forced materialization
// wins (typically queries whose inner CTE is recomputed many times due to
// inlining and produces non-trivial row counts).
EnableMaterializedCTEs bool
}
GenerateSQLOptions tunes codegen behavior for GenerateSQLWithOptions and GenerateListSQLWithOptions. The zero value matches the default melange behavior.
type GeneratedSQL ¶
type GeneratedSQL struct {
// Functions contains CREATE OR REPLACE FUNCTION statements
// for each specialized check function (check_{type}_{relation}).
Functions []string
// NoWildcardFunctions contains CREATE OR REPLACE FUNCTION statements
// for no-wildcard variants (check_{type}_{relation}_nw).
// These skip wildcard matching for performance-critical paths.
NoWildcardFunctions []string
// NoWildcardIndex maps object_type -> relation -> whether a distinct _nw
// variant was emitted (true) or the base function is reused (false). It is the
// buildNoWildcardIndex result computed once during generation, exposed so
// callers (e.g. CollectNamedFunctions) need not recompute the per-relation
// wildcard-reachability walk.
NoWildcardIndex map[string]map[string]bool
// Dispatcher contains the check_permission dispatcher function
// that routes requests to specialized functions based on object type and relation.
Dispatcher string
// DispatcherNoWildcard contains the check_permission_nw dispatcher.
DispatcherNoWildcard string
// BulkDispatcher contains the check_permission_bulk function that evaluates
// multiple permission checks in a single SQL call using UNION ALL branches.
BulkDispatcher string
// ExplainFunctions contains CREATE OR REPLACE FUNCTION statements for the
// per-relation explain_{type}_{relation} functions. Each returns JSONB
// shaped to melange.Trace and is the codegen companion to check_*.
ExplainFunctions []string
// ExplainDispatcher contains the explain_permission public + internal
// functions that route to per-relation explain_* by (object_type, relation).
// Returns a structurally valid JSONB Trace even for unknown pairs so
// callers can deserialise without special-casing.
ExplainDispatcher string
// ExplainEligible records the (object_type, relation) pairs for which an
// explain function was generated. CollectNamedFunctions reads this
// directly; hand-built GeneratedSQL values must populate it via
// ComputeExplainEligibility(analyses) before calling CollectNamedFunctions.
ExplainEligible map[string]map[string]bool
// ExpandFunctions contains CREATE OR REPLACE FUNCTION statements for the
// per-relation expand_{type}_{relation} functions. Each returns the
// OpenFGA-shaped UsersetTree JSONB documented in melange/expand.go.
ExpandFunctions []string
// ExpandDispatcher contains the expand_permission public + internal
// functions that route to per-relation expand_* by (object_type, relation).
// Returns an empty Leaf.Users sentinel for unknown / not-yet-supported
// pairs so OpenFGA tooling deserialises without special-casing.
ExpandDispatcher string
// ExpandEligible records the (object_type, relation) pairs for which an
// expand function was generated. Stage 2 slice 2.1 gates many shapes
// (TTU, intersection, exclusion, usersets, wildcards, complex usersets)
// out — those route to the dispatcher's empty-leaf sentinel until the
// follow-up slices land.
ExpandEligible map[string]map[string]bool
// IndexRecommendations lists composite indexes that make the generated
// functions efficient against melange_tuples. Advisory only — users
// translate the DDL to their source tables. See RecommendIndexes.
IndexRecommendations []IndexRecommendation
}
GeneratedSQL contains all SQL generated for a schema. This is applied atomically during migration to ensure consistent state.
func GenerateSQL ¶
func GenerateSQL(analyses []RelationAnalysis, inline InlineSQLData, databaseSchema string) (GeneratedSQL, error)
GenerateSQL generates specialized SQL functions for all relations in the schema using default options. See GenerateSQLWithOptions for tunable behavior.
For each relation, it generates:
- A specialized check function that evaluates permission checks efficiently
- A no-wildcard variant for scenarios where wildcards are disallowed
- Dispatcher functions that route to the appropriate specialized function
The inline parameter provides precomputed closure and userset data that is inlined into the generated functions as VALUES clauses, eliminating runtime table joins for this metadata.
Returns an error if any function fails to generate, though this is rare as the analysis phase validates generation feasibility.
func GenerateSQLWithOptions ¶ added in v0.8.3
func GenerateSQLWithOptions(analyses []RelationAnalysis, inline InlineSQLData, databaseSchema string, _ GenerateSQLOptions) (GeneratedSQL, error)
GenerateSQLWithOptions is the option-aware variant of GenerateSQL.
Currently the option set only affects list-function codegen (via GenerateListSQLWithOptions); check-function output is independent of the options today. The option is accepted here to keep a single public surface the migrator can configure once.
type GenerationCapabilities ¶
type GenerationCapabilities = analysis.GenerationCapabilities
analysis types
type ImpliedFunctionCheck ¶
ImpliedFunctionCheck represents a check that calls another check function.
type IndexRecommendation ¶ added in v0.8.3
type IndexRecommendation struct {
// BaseTable is the table the DDL targets. Always "melange_tuples" in the
// schema-driven flow; doctor integration may rewrite to source tables.
BaseTable string
// Columns is the ordered composite — earliest-most-selective first.
Columns []string
// WhereClause is an optional partial-index predicate. Empty for full indexes.
WhereClause string
// BenefitsFunctions lists generated function names that match this index's
// access pattern. Sorted alphabetically for stable output.
BenefitsFunctions []string
// DDL is the rendered CREATE INDEX IF NOT EXISTS statement.
DDL string
}
IndexRecommendation describes a composite index that would make one or more generated functions efficient against melange_tuples.
The recommendation targets melange_tuples as if it were a base table (Option A from INDEX_RECOMMENDATIONS.md): users translate the DDL to whichever source tables back the UNION ALL branches of their view, since PostgreSQL cannot create indexes on views directly.
func RecommendIndexes ¶ added in v0.8.3
func RecommendIndexes(analyses []RelationAnalysis) []IndexRecommendation
RecommendIndexes returns the minimum set of composite indexes that make the generated check and list functions efficient. Output is sorted by DDL string for deterministic results.
Two index families are emitted per (type, relation) with CheckAllowed or ListAllowed:
- Object-keyed (object_type, object_id, relation, subject_type, subject_id): covers check_* and list_*_sub access patterns.
- Subject-keyed (subject_type, subject_id, relation, object_type, object_id): covers list_*_obj access patterns.
Relations with HasWildcard additionally get a partial index over wildcard rows so wildcard membership lookups don't scan the whole subject_id space.
Recommendations are deduplicated: an index that benefits multiple functions appears once with all function names in BenefitsFunctions.
type IntersectionGroupCheck ¶
type IntersectionGroupCheck struct {
Parts []IntersectionPartCheck
}
IntersectionGroupCheck represents an AND group where all checks must pass.
type IntersectionGroupInfo ¶
type IntersectionGroupInfo = analysis.IntersectionGroupInfo
analysis types
type IntersectionPartCheck ¶
type IntersectionPartCheck struct {
Relation string
ExcludedRelation string
IsThis bool // [user]-direct grant probe at the wrapping relation
IsParent bool
ParentRelation string
LinkingRelation string
Check Expr
}
IntersectionPartCheck represents one part of an intersection check.
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 ¶
type ListDispatcherCase struct {
DatabaseSchema string
ObjectType string
Relation string
FunctionName string
}
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, databaseSchema string) (ListGeneratedSQL, error)
GenerateListSQL generates specialized SQL functions for list operations using default options. See GenerateListSQLWithOptions for tunable behavior.
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.
func GenerateListSQLWithOptions ¶ added in v0.8.3
func GenerateListSQLWithOptions(analyses []RelationAnalysis, inline InlineSQLData, databaseSchema string, opts GenerateSQLOptions) (ListGeneratedSQL, error)
GenerateListSQLWithOptions is the option-aware variant of GenerateListSQL. The opts.EnableMaterializedCTEs flag is threaded into each ListPlan so render functions can decide whether to emit "AS MATERIALIZED" on paged/returned.
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 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
// AllowedLinkingTypesSlice contains the same types as AllowedLinkingTypes but as a slice.
// Used for typed DSL expressions (In{Expr: ..., Values: AllowedLinkingTypesSlice}).
AllowedLinkingTypesSlice []string
// 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
// IsClosurePattern indicates this TTU pattern was inherited from an implied relation.
// For example, if `can_read: reader` and `reader: repo_admin from owner but not restricted`,
// then can_read's TTU "owner -> repo_admin" is a closure pattern with SourceRelation="reader".
// Closure patterns must verify through the source relation to honor its exclusions/intersections.
IsClosurePattern bool
SourceRelation string // The relation this TTU was inherited from (for closure patterns)
}
ListParentRelationData contains data for rendering TTU pattern expansion in list templates. For a pattern like "viewer from parent", this represents the parent traversal.
type ListPlan ¶
type ListPlan struct {
// Input data
Analysis RelationAnalysis
Inline InlineSQLData
DatabaseSchema string
// Function identity
FunctionName string
ObjectType string
Relation string
// Computed relation lists
RelationList []string // Relations for tuple lookup (self + simple closure)
AllSatisfyingRelations []string // All relations that satisfy this one (for list_subjects)
AllowedSubjectTypes []string // Subject types allowed for this relation
ComplexClosure []string // Complex closure relations (excluding intersection)
// Feature configuration
AllowWildcard bool // Whether the relation can surface '*' (direct or TTU-reachable)
Exclusions ExclusionConfig // Exclusion rules configuration
// Feature flags (derived from analysis)
HasUserset bool
HasExclusion bool
HasIntersection bool
HasRecursive bool
// Eligibility and strategy from unified analysis
Capabilities GenerationCapabilities
Strategy ListStrategy
// Additional flags for routing
HasUsersetSubject bool // Has userset subject matching capability
HasUsersetPatterns bool // Has userset patterns to expand
HasComplexUsersets bool // Has userset patterns requiring check_permission calls
HasStandaloneAccess bool // Has standalone access paths (not constrained by intersection)
// Optimization flags
UseCTEExclusion bool // Use CTE-based exclusion optimization (precompute + anti-join)
// Analysis lookup for checking parent relation complexity (TTU optimization)
// Maps "objectType.relation" -> *RelationAnalysis
// Used by TTU block generation to determine if parent relations are simple or complex
AnalysisLookup map[string]*RelationAnalysis
// EnableMaterializedCTEs, when true, emits AS MATERIALIZED on
// multi-referenced CTEs in generated list functions. Default false: PG
// chooses inlining vs materialization on its own, which on benchmarked
// production-scale workloads outperformed forced materialization. Wired
// from GenerateSQLOptions for callers that profile a workload where
// forced materialization helps.
EnableMaterializedCTEs bool
}
ListPlan contains all computed data needed to generate a list function.
func BuildListObjectsPlanWithLookup ¶
func BuildListObjectsPlanWithLookup(a RelationAnalysis, inline InlineSQLData, databaseSchema string, lookup map[string]*RelationAnalysis) ListPlan
BuildListObjectsPlanWithLookup creates a plan with analysis lookup for TTU optimization.
func BuildListSubjectsPlanWithLookup ¶
func BuildListSubjectsPlanWithLookup(a RelationAnalysis, inline InlineSQLData, databaseSchema string, lookup map[string]*RelationAnalysis) ListPlan
BuildListSubjectsPlanWithLookup creates a plan with analysis lookup for TTU optimization.
func (ListPlan) ExcludeWildcard ¶
func (ListPlan) FeaturesString ¶
func (ListPlan) MaterializeCTEs ¶ added in v0.8.3
MaterializeCTEs reports whether multi-referenced CTEs in generated list functions should render with "AS MATERIALIZED". Default is false (let PG decide); set GenerateSQLOptions.EnableMaterializedCTEs to opt in.
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 NamedFunction ¶ added in v0.7.4
NamedFunction pairs a specialized function name with its generated SQL body. Dispatcher functions are excluded from this set; see CollectNamedFunctions. The SQL field is used verbatim for checksum computation and for emitting changed-only migrations.
func CollectDispatcherFunctions ¶ added in v0.8.5
func CollectDispatcherFunctions(generatedSQL GeneratedSQL, listSQL ListGeneratedSQL) []NamedFunction
CollectDispatcherFunctions returns the dispatcher functions paired with their SQL, named by their public entry point. Dispatchers are excluded from CollectNamedFunctions, so without these entries a codegen change that only alters dispatcher SQL is invisible to checksum-based skip detection.
func CollectNamedFunctions ¶ added in v0.7.4
func CollectNamedFunctions( generatedSQL GeneratedSQL, listSQL ListGeneratedSQL, analyses []RelationAnalysis, ) []NamedFunction
CollectNamedFunctions returns all specialized functions paired with their SQL. Dispatchers are excluded here; the migrator checksums them separately via CollectDispatcherFunctions so a dispatcher-only codegen change still defeats the phase-2 skip.
The analyses slice must be the same slice, in the same order, passed to GenerateSQL and GenerateListSQL that produced generatedSQL and listSQL. The function walks all three in lockstep; mismatched ordering will silently produce incorrect name-to-SQL pairings.
type NodeJSONArgs ¶ added in v0.8.4
type NodeJSONArgs struct {
// Label is a SQL expression for the human-readable description.
// Wrap literals with sqldsl.QuoteLiteral.
Label string
// Evidence, Children, Users should evaluate to JSONB arrays
// (commonly jsonb_agg(...) or jsonb_build_array(...)).
Evidence string
Children string
Users string
// Result is a SQL expression evaluating to a boolean. Populated on
// Explain nodes; omit on safety-stop nodes (cycle / truncated).
Result string
}
NodeJSONArgs carries the optional pieces of a Node JSONB. All fields are SQL expressions — column references, sqldsl.QuoteLiteral-quoted literals, jsonb_build_array(...) calls, etc. Empty fields are omitted from the emitted object so the JSON shape matches the `omitempty` Go tags.
type ParentRelationBlock ¶
type ParentRelationBlock struct {
LinkingRelation string
ParentRelation string
AllowedLinkingTypes []string
Query SelectStmt
}
ParentRelationBlock represents a TTU check through a parent relation.
type RecursiveBlockSet ¶
type RecursiveBlockSet struct {
BaseBlocks []TypedQueryBlock
RecursiveBlock *TypedQueryBlock
SelfCandidateBlock *TypedQueryBlock
// HoistedCTEs are shared list_*_obj computations lifted out of the base
// blocks. The same list_<parent>_obj(p_subject_type, p_subject_id, NULL,
// NULL) call was previously inlined in both the userset-subject arm and the
// cross-type-TTU subject-first arm; these CTEs compute each distinct call
// once and the arms reference them. Rendered before the accessible CTE.
HoistedCTEs []CTEDef
}
RecursiveBlockSet contains blocks for a recursive list function.
func BuildListObjectsRecursiveBlocks ¶
func BuildListObjectsRecursiveBlocks(plan ListPlan) (RecursiveBlockSet, error)
BuildListObjectsRecursiveBlocks builds blocks for a recursive list_objects function. This handles TTU patterns with depth tracking and recursive CTEs.
type SelfRefUsersetBlockSet ¶
type SelfRefUsersetBlockSet struct {
// BaseBlocks are the base case blocks (depth=0) in the CTE
BaseBlocks []TypedQueryBlock
// RecursiveBlock is the recursive term block that expands self-referential usersets
RecursiveBlock *TypedQueryBlock
// SelfCandidateBlock is added outside the CTE (UNION with CTE result)
SelfCandidateBlock *TypedQueryBlock
}
SelfRefUsersetBlockSet contains blocks for a self-referential userset list function. These blocks are wrapped in a recursive CTE for member expansion.
func BuildListObjectsSelfRefUsersetBlocks ¶
func BuildListObjectsSelfRefUsersetBlocks(plan ListPlan) (SelfRefUsersetBlockSet, error)
BuildListObjectsSelfRefUsersetBlocks builds blocks for a self-referential userset list_objects function.
type SelfRefUsersetSubjectsBlockSet ¶
type SelfRefUsersetSubjectsBlockSet struct {
// UsersetFilterBlocks are blocks for the userset filter path (when p_subject_type contains '#')
UsersetFilterBlocks []TypedQueryBlock
// UsersetFilterSelfBlock is the self-candidate block for userset filter path
UsersetFilterSelfBlock *TypedQueryBlock
// UsersetFilterRecursiveBlock is the recursive block for userset filter expansion
UsersetFilterRecursiveBlock *TypedQueryBlock
// RegularBlocks are blocks for the regular path (individual subjects)
RegularBlocks []TypedQueryBlock
// UsersetObjectsBaseBlock is the base block for userset objects CTE
UsersetObjectsBaseBlock *TypedQueryBlock
// UsersetObjectsRecursiveBlock is the recursive block for userset objects CTE
UsersetObjectsRecursiveBlock *TypedQueryBlock
}
SelfRefUsersetSubjectsBlockSet contains blocks for a self-referential userset list_subjects function. This includes separate blocks for userset filter path and regular path.
func BuildListSubjectsSelfRefUsersetBlocks ¶
func BuildListSubjectsSelfRefUsersetBlocks(plan ListPlan) (SelfRefUsersetSubjectsBlockSet, error)
BuildListSubjectsSelfRefUsersetBlocks builds blocks for a self-referential userset list_subjects function.
type SubjectsIntersectionBlockSet ¶
type SubjectsIntersectionBlockSet struct {
RegularCandidateBlocks []TypedQueryBlock
UsersetFilterCandidateBlocks []TypedQueryBlock
UsersetFilterSelfBlock *TypedQueryBlock
}
SubjectsIntersectionBlockSet contains blocks for an intersection list_subjects function. Unlike recursive which uses check_permission_internal within queries, intersection gathers candidates then filters with check_permission at the end.
func BuildListSubjectsIntersectionBlocks ¶
func BuildListSubjectsIntersectionBlocks(plan ListPlan) SubjectsIntersectionBlockSet
type SubjectsRecursiveBlockSet ¶
type SubjectsRecursiveBlockSet struct {
RegularBlocks []TypedQueryBlock
RegularTTUBlocks []TypedQueryBlock
UsersetFilterBlocks []TypedQueryBlock
UsersetFilterSelfBlock *TypedQueryBlock
ParentRelations []ListParentRelationData
}
SubjectsRecursiveBlockSet contains blocks for a recursive list_subjects function.
func BuildListSubjectsRecursiveBlocks ¶
func BuildListSubjectsRecursiveBlocks(plan ListPlan) (SubjectsRecursiveBlockSet, error)
BuildListSubjectsRecursiveBlocks builds blocks for a recursive list_subjects function.
type TraceNodeType ¶ added in v0.8.4
type TraceNodeType string
TraceNodeType mirrors melange.NodeType. It is duplicated here rather than imported from the runtime module so lib/sqlgen continues to depend only on stdlib + sqldsl + analysis (the runtime module is built on top of sqlgen, not the other way around).
const ( TraceNodeDirect TraceNodeType = "direct" TraceNodeImplied TraceNodeType = "implied" TraceNodeUserset TraceNodeType = "userset" TraceNodeTTU TraceNodeType = "ttu" TraceNodeUnion TraceNodeType = "union" TraceNodeIntersection TraceNodeType = "intersection" TraceNodeExclusion TraceNodeType = "exclusion" TraceNodeWildcard TraceNodeType = "wildcard" TraceNodeCycle TraceNodeType = "cycle" TraceNodeTruncated TraceNodeType = "truncated" )
type TypedQueryBlock ¶
type TypedQueryBlock struct {
Comments []string
Query SelectStmt
// Propagatable indicates whether results from this block should seed
// the recursive step in a recursive CTE. Only results from relations
// that participate in self-referential TTU patterns should propagate.
// For example, with "can_view: viewer or folder_viewer" where only
// viewer has "viewer from parent", blocks matching folder_viewer
// should NOT propagate through the parent chain.
Propagatable bool
}
TypedQueryBlock represents a query with optional comments. Uses SelectStmt for type-safe DSL construction.
Source Files
¶
- check_blocks.go
- check_functions.go
- check_plan.go
- check_queries.go
- check_render.go
- compile.go
- doc.go
- exclusion.go
- expand_blocks.go
- expand_functions.go
- expand_render.go
- explain_functions.go
- explain_render.go
- exports.go
- indexes.go
- inline_filter.go
- list_composition.go
- list_functions.go
- list_helpers.go
- list_objects_blocks.go
- list_objects_blocks_composed.go
- list_objects_blocks_recursive.go
- list_objects_blocks_selfref.go
- list_objects_render.go
- list_objects_render_composed.go
- list_objects_render_depth.go
- list_objects_render_recursive.go
- list_objects_render_selfref.go
- list_plan.go
- list_shared_blocks.go
- list_shared_render.go
- list_subjects_blocks.go
- list_subjects_blocks_composed.go
- list_subjects_blocks_intersection.go
- list_subjects_blocks_recursive.go
- list_subjects_blocks_selfref.go
- list_subjects_closure_cte.go
- list_subjects_render.go
- list_subjects_render_composed.go
- list_subjects_render_depth.go
- list_subjects_render_helpers.go
- list_subjects_render_intersection.go
- list_subjects_render_recursive.go
- list_subjects_render_selfref.go
- permission.go
- trace_blocks.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package analysis provides relation analysis and strategy selection for SQL code generation.
|
Package analysis provides relation analysis and strategy selection for SQL code generation. |
|
Package inline generates VALUES clauses for inlining metadata into SQL functions.
|
Package inline generates VALUES clauses for inlining metadata into SQL functions. |
|
Package plpgsql provides PL/pgSQL function builder types.
|
Package plpgsql provides PL/pgSQL function builder types. |
|
Package sqldsl provides a type-safe DSL for building PostgreSQL queries.
|
Package sqldsl provides a type-safe DSL for building PostgreSQL queries. |
|
Package tuples provides tuple-table specific builders and helpers.
|
Package tuples provides tuple-table specific builders and helpers. |