sqlgen

package
v0.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 11, 2026 License: MIT Imports: 5 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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.

View Source
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

func Ident(name string) string

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 ListObjectsIntersectionClosureQuery

func ListObjectsIntersectionClosureQuery(functionName string) (string, error)

func ListObjectsIntersectionClosureValidatedQuery

func ListObjectsIntersectionClosureValidatedQuery(objectType, relation, functionName string) (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 ListSubjectsIntersectionClosureQuery

func ListSubjectsIntersectionClosureQuery(functionName, subjectTypeExpr string) (string, error)

func ListSubjectsIntersectionClosureValidatedQuery

func ListSubjectsIntersectionClosureValidatedQuery(objectType, relation, functionName, functionSubjectTypeExpr, checkSubjectTypeExpr, objectIDExpr string) (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

func RenderDSLExprs(exprs []Expr) []string

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

type Alias struct {
	Expr Expr
	Name string
}

Alias wraps an expression with an alias (expr AS alias).

func SelectAs

func SelectAs(expr Expr, alias string) Alias

SelectAs creates an aliased column expression (expr AS alias). Shorthand for Alias{Expr: expr, Name: alias}.

func (Alias) SQL

func (a Alias) SQL() string

SQL renders the aliased expression.

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.

func And

func And(exprs ...Expr) AndExpr

And creates an AND expression from multiple expressions.

func (AndExpr) SQL

func (a AndExpr) SQL() string

SQL renders the AND expression.

type Bool

type Bool bool

Bool represents a boolean literal.

func (Bool) SQL

func (b Bool) SQL() string

SQL renders the boolean.

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 Col

type Col struct {
	Table  string
	Column string
}

Col represents a table column reference (e.g., t.object_id).

func (Col) SQL

func (c Col) SQL() string

SQL renders the column reference.

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 Concat

type Concat struct {
	Parts []Expr
}

Concat represents SQL string concatenation (||).

func (Concat) SQL

func (c Concat) SQL() string

SQL renders the concatenation.

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 DirectCheckInput struct {
	ObjectType    string
	Relations     []string
	SubjectTypes  []string
	AllowWildcard bool
}

type DispatcherCase

type DispatcherCase struct {
	ObjectType        string
	Relation          string
	CheckFunctionName string
}

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[]).

func (EmptyArray) SQL

func (EmptyArray) SQL() string

SQL renders the empty array.

type Eq

type Eq struct {
	Left  Expr
	Right Expr
}

Eq represents an equality comparison (=).

func (Eq) SQL

func (e Eq) SQL() string

SQL renders the equality comparison.

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

type ExclusionCheckData struct {
	ObjectType       string
	ExcludedRelation string
	SubjectIDCheck   string
}

ExclusionCheckData contains data for rendering exclusion check template.

type ExclusionCheckInput

type ExclusionCheckInput struct {
	ObjectType       string
	ExcludedRelation string
	AllowWildcard    bool
}

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).

func (Exists) SQL

func (e Exists) SQL() string

SQL renders the EXISTS expression.

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

func NormalizedUsersetSubject(subjectID, relation Expr) Expr

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

func SimpleExclusion(objectType, relation string, objectID, subjectType, subjectID Expr) Expr

SimpleExclusion creates a NOT EXISTS exclusion for a simple "but not" rule.

func SubjectIDMatch

func SubjectIDMatch(column, subjectID Expr, allowWildcard bool) Expr

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 Func

type Func struct {
	Name string
	Args []Expr
}

Func represents a SQL function call.

func (Func) SQL

func (f Func) SQL() string

SQL renders the function call.

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 Gt

type Gt struct {
	Left  Expr
	Right Expr
}

Gt represents a greater-than comparison (>).

func (Gt) SQL

func (g Gt) SQL() string

SQL renders the greater-than comparison.

type Gte

type Gte struct {
	Left  Expr
	Right Expr
}

Gte represents a greater-than-or-equal comparison (>=).

func (Gte) SQL

func (g Gte) SQL() string

SQL renders the greater-than-or-equal comparison.

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

func (HasUserset) SQL

func (h HasUserset) SQL() string

SQL renders the position check expression.

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 In

type In struct {
	Expr   Expr
	Values []string
}

In represents an IN clause for string values.

func (In) SQL

func (i In) SQL() string

SQL renders the IN clause.

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 Int

type Int int

Int represents an integer literal.

func (Int) SQL

func (i Int) SQL() string

SQL renders the integer.

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 IsNotNull

type IsNotNull struct {
	Expr Expr
}

IsNotNull represents IS NOT NULL check.

func (IsNotNull) SQL

func (i IsNotNull) SQL() string

SQL renders the IS NOT NULL expression.

type IsNull

type IsNull struct {
	Expr Expr
}

IsNull represents IS NULL check.

func (IsNull) SQL

func (i IsNull) SQL() string

SQL renders the IS NULL expression.

type IsWildcard

type IsWildcard struct {
	Source Expr
}

IsWildcard checks if an expression equals the wildcard value "*".

func (IsWildcard) SQL

func (w IsWildcard) SQL() string

SQL renders the wildcard check.

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.

func (JoinClause) SQL

func (j JoinClause) SQL() string

SQL renders the JOIN clause.

type LateralFunction

type LateralFunction struct {
	Name  string
	Args  []Expr
	Alias string
}

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

type ListDispatcherCase struct {
	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) (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 ListObjectsSelfCandidateInput

type ListObjectsSelfCandidateInput struct {
	ObjectType    string
	Relation      string
	ClosureValues string
}

type ListObjectsUsersetPatternComplexInput

type ListObjectsUsersetPatternComplexInput struct {
	ObjectType       string
	SubjectType      string
	SubjectRelation  string
	SourceRelations  []string
	IsClosurePattern bool
	SourceRelation   string
	Exclusions       ExclusionConfig
}

type ListObjectsUsersetPatternSimpleInput

type ListObjectsUsersetPatternSimpleInput struct {
	ObjectType          string
	SubjectType         string
	SubjectRelation     string
	SourceRelations     []string
	SatisfyingRelations []string
	AllowedSubjectTypes []string
	AllowWildcard       bool
	IsClosurePattern    bool
	SourceRelation      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 ListSubjectsComplexClosureInput

type ListSubjectsComplexClosureInput struct {
	ObjectType      string
	Relation        string
	ObjectIDExpr    string
	SubjectTypeExpr string
	ExcludeWildcard bool
	Exclusions      ExclusionConfig
}

type ListSubjectsDirectInput

type ListSubjectsDirectInput struct {
	ObjectType      string
	RelationList    []string
	ObjectIDExpr    string
	SubjectTypeExpr string
	ExcludeWildcard bool
	Exclusions      ExclusionConfig
}

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 ListSubjectsSelfCandidateInput

type ListSubjectsSelfCandidateInput struct {
	ObjectType         string
	Relation           string
	ObjectIDExpr       string
	FilterTypeExpr     string
	FilterRelationExpr string
	ClosureValues      string
	ExtraPredicatesSQL []string // Raw SQL predicate strings
}

type ListSubjectsUsersetFilterInput

type ListSubjectsUsersetFilterInput struct {
	ObjectType          string
	RelationList        []string
	AllowedSubjectTypes []string
	ObjectIDExpr        string
	FilterTypeExpr      string
	FilterRelationExpr  string
	ClosureValues       string
	UseTypeGuard        bool
	ExtraPredicatesSQL  []string // Raw SQL predicate strings
}

type ListSubjectsUsersetPatternComplexInput

type ListSubjectsUsersetPatternComplexInput struct {
	ObjectType       string
	SubjectType      string
	SubjectRelation  string
	SourceRelations  []string
	ObjectIDExpr     string
	SubjectTypeExpr  string
	IsClosurePattern bool
	SourceRelation   string
	Exclusions       ExclusionConfig
}

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 Lit

type Lit string

Lit represents a literal string value (auto-quoted with single quotes).

func (Lit) SQL

func (l Lit) SQL() string

SQL renders the literal with single quotes.

type Lt

type Lt struct {
	Left  Expr
	Right Expr
}

Lt represents a less-than comparison (<).

func (Lt) SQL

func (l Lt) SQL() string

SQL renders the less-than comparison.

type Lte

type Lte struct {
	Left  Expr
	Right Expr
}

Lte represents a less-than-or-equal comparison (<=).

func (Lte) SQL

func (l Lte) SQL() string

SQL renders the less-than-or-equal comparison.

type Ne

type Ne struct {
	Left  Expr
	Right Expr
}

Ne represents a not-equal comparison (<>).

func (Ne) SQL

func (n Ne) SQL() string

SQL renders the not-equal comparison.

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

func (NoUserset) SQL

func (n NoUserset) SQL() string

SQL renders the position check expression.

type NotExists

type NotExists struct {
	Query interface{ SQL() string }
}

NotExists represents a NOT EXISTS subquery.

func (NotExists) SQL

func (n NotExists) SQL() string

SQL renders the NOT EXISTS expression.

type NotExpr

type NotExpr struct {
	Expr Expr
}

NotExpr represents a logical NOT of an expression.

func Not

func Not(expr Expr) NotExpr

Not creates a NOT expression.

func (NotExpr) SQL

func (n NotExpr) SQL() string

SQL renders the NOT expression.

type Null

type Null struct{}

Null represents SQL NULL.

func (Null) SQL

func (Null) SQL() string

SQL renders NULL.

type ObjectRef

type ObjectRef struct {
	Type Expr
	ID   Expr
}

ObjectRef represents an object reference (type + id). An object is always identified by a type and an id.

func LiteralObject

func LiteralObject(objectType string, id Expr) ObjectRef

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.

func Or

func Or(exprs ...Expr) OrExpr

Or creates an OR expression from multiple expressions.

func (OrExpr) SQL

func (o OrExpr) SQL() string

SQL renders the OR expression.

type Param

type Param string

Param represents a function parameter (e.g., p_subject_type, p_object_id).

func (Param) SQL

func (p Param) SQL() string

SQL renders the parameter.

type Paren

type Paren struct {
	Expr Expr
}

Paren wraps an expression in parentheses.

func (Paren) SQL

func (p Paren) SQL() string

SQL renders the parenthesized expression.

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 Raw

type Raw string

Raw is an escape hatch for arbitrary SQL expressions.

func (Raw) SQL

func (r Raw) SQL() string

SQL renders the raw SQL as-is.

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) Exists

func (s SelectStmt) Exists() string

Exists wraps a query in EXISTS(...).

func (SelectStmt) NotExists

func (s SelectStmt) NotExists() string

NotExists wraps a query in NOT EXISTS(...).

func (SelectStmt) SQL

func (s SelectStmt) SQL() string

SQL renders the SELECT statement.

type SubjectRef

type SubjectRef struct {
	Type Expr
	ID   Expr
}

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

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

type TableRef struct {
	Name  string
	Alias string
}

TableRef wraps a raw table name for use as a TableExpr.

func TableAs

func TableAs(name, alias string) TableRef

TableAs creates a table reference with an alias.

func (TableRef) TableAlias

func (t TableRef) TableAlias() string

TableAlias implements TableExpr.

func (TableRef) TableSQL

func (t TableRef) TableSQL() string

TableSQL implements 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) SQL

func (q *TupleQuery) SQL() string

SQL renders the query to a SQL string.

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 UsersetCheckInput struct {
	ObjectType          string
	Relation            string
	SubjectType         string
	SubjectRelation     string
	SatisfyingRelations []string
	AllowWildcard       bool
}

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 UsersetSubjectComputedCheckInput

type UsersetSubjectComputedCheckInput struct {
	ObjectType    string
	Relation      string
	ClosureValues string
	UsersetValues string
}

type UsersetSubjectSelfCheckInput

type UsersetSubjectSelfCheckInput struct {
	ObjectType    string
	Relation      string
	ClosureValues string
}

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) SQL

func (v ValuesTable) SQL() string

SQL renders the VALUES table expression.

func (ValuesTable) TableAlias

func (v ValuesTable) TableAlias() string

TableAlias implements TableExpr.

func (ValuesTable) TableSQL

func (v ValuesTable) TableSQL() string

TableSQL implements TableExpr.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL