mputedAttributeIdentifier   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nop 1
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
package compiler
2
3
import (
4
	"errors"
5
	"fmt"
6
	"strings"
7
8
	"github.com/google/cel-go/cel"
9
10
	"github.com/Permify/permify/pkg/dsl/ast"
11
	"github.com/Permify/permify/pkg/dsl/token"
12
	"github.com/Permify/permify/pkg/dsl/utils"
13
	base "github.com/Permify/permify/pkg/pb/base/v1"
14
)
15
16
// Compiler compiles an AST schema into a list of entity definitions.
17
type Compiler struct {
18
	// The AST schema to be compiled
19
	schema *ast.Schema
20
	// Whether to skip reference validation during compilation
21
	withReferenceValidation bool
22
}
23
24
// NewCompiler returns a new Compiler instance with the given schema and reference validation flag.
25
func NewCompiler(w bool, sch *ast.Schema) *Compiler {
26
	return &Compiler{
27
		withReferenceValidation: w,
28
		schema:                  sch,
29
	}
30
}
31
32
// Compile compiles the schema into a list of entity definitions.
33
// Returns a slice of EntityDefinition pointers and an error, if any.
34
func (t *Compiler) Compile() ([]*base.EntityDefinition, []*base.RuleDefinition, error) {
35
	// If withoutReferenceValidation is not set to true, validate the schema for reference errors.
36
	if t.withReferenceValidation {
37
		err := t.schema.Validate()
38
		if err != nil {
39
			return nil, nil, err
40
		}
41
	}
42
43
	// Create an empty slice to hold the entity definitions.
44
	entities := make([]*base.EntityDefinition, 0, len(t.schema.Statements))
45
	rules := make([]*base.RuleDefinition, 0, len(t.schema.Statements))
46
47
	// Loop through each statement in the schema.
48
	for _, statement := range t.schema.Statements {
49
		switch v := statement.(type) {
50
		case *ast.EntityStatement:
51
			// Compile the EntityStatement into an EntityDefinition.
52
			entityDef, err := t.compileEntity(v)
53
			if err != nil {
54
				return nil, nil, err
55
			}
56
57
			// Append the EntityDefinition to the slice of entity definitions.
58
			entities = append(entities, entityDef)
59
		case *ast.RuleStatement:
60
			// Compile the RuleStatement into a RuleDefinition.
61
			ruleDef, err := t.compileRule(v)
62
			if err != nil {
63
				return nil, nil, err
64
			}
65
66
			// Append the RuleDefinition to the slice of rule definitions.
67
			rules = append(rules, ruleDef)
68
		default:
69
			return nil, nil, errors.New("invalid statement")
70
		}
71
	}
72
73
	return entities, rules, nil
74
}
75
76
// compile - compiles an EntityStatement into an EntityDefinition
77
func (t *Compiler) compileEntity(sc *ast.EntityStatement) (*base.EntityDefinition, error) {
78
	// Initialize the entity definition
79
	entityDefinition := &base.EntityDefinition{
80
		Name:        sc.Name.Literal,
81
		Relations:   map[string]*base.RelationDefinition{},
82
		Attributes:  map[string]*base.AttributeDefinition{},
83
		Permissions: map[string]*base.PermissionDefinition{},
84
		References:  map[string]base.EntityDefinition_Reference{},
85
	}
86
87
	// Compile relations
88
	for _, rs := range sc.RelationStatements {
89
		// Cast the relation statement
90
		st, okRs := rs.(*ast.RelationStatement)
91
		if !okRs {
92
			return nil, compileError(token.PositionInfo{}, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
93
		}
94
95
		// Initialize the relation definition
96
		relationDefinition := &base.RelationDefinition{
97
			Name:               st.Name.Literal,
98
			RelationReferences: []*base.RelationReference{},
99
		}
100
101
		// Compile the relation types
102
		for _, rts := range st.RelationTypes {
103
			relationDefinition.RelationReferences = append(relationDefinition.RelationReferences, &base.RelationReference{
104
				Type:     rts.Type.Literal,
105
				Relation: rts.Relation.Literal,
106
			})
107
		}
108
109
		// Add the relation definition and reference
110
		entityDefinition.Relations[relationDefinition.GetName()] = relationDefinition
111
		entityDefinition.References[relationDefinition.GetName()] = base.EntityDefinition_REFERENCE_RELATION
112
	}
113
114
	for _, as := range sc.AttributeStatements {
115
		st, okAs := as.(*ast.AttributeStatement)
116
		if !okAs {
117
			return nil, compileError(token.PositionInfo{}, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
118
		}
119
120
		typ, err := getArgumentTypeIfExist(st.AttributeType)
121
		if err != nil {
122
			return nil, err
123
		}
124
125
		attributeDefinition := &base.AttributeDefinition{
126
			Name: st.Name.Literal,
127
			Type: typ,
128
		}
129
130
		entityDefinition.Attributes[attributeDefinition.GetName()] = attributeDefinition
131
		entityDefinition.References[attributeDefinition.GetName()] = base.EntityDefinition_REFERENCE_ATTRIBUTE
132
	}
133
134
	// Compile permissions
135
	for _, ps := range sc.PermissionStatements {
136
		// Cast the permission statement
137
		st, okAs := ps.(*ast.PermissionStatement)
138
		if !okAs {
139
			return nil, compileError(token.PositionInfo{}, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
140
		}
141
142
		// Compile the child expression
143
		ch, err := t.compileExpressionStatement(entityDefinition.GetName(), st.ExpressionStatement.(*ast.ExpressionStatement))
144
		if err != nil {
145
			return nil, err
146
		}
147
148
		// Initialize the permission definition and reference
149
		permissionDefinition := &base.PermissionDefinition{
150
			Name:  st.Name.Literal,
151
			Child: ch,
152
		}
153
		entityDefinition.Permissions[permissionDefinition.GetName()] = permissionDefinition
154
		entityDefinition.References[permissionDefinition.GetName()] = base.EntityDefinition_REFERENCE_PERMISSION
155
	}
156
157
	return entityDefinition, nil
158
}
159
160
// compileRule compiles an ast.RuleStatement into a base.RuleDefinition object.
161
// It takes an *ast.RuleStatement as input, processes its arguments, and
162
// returns a *base.RuleDefinition or an error.
163
func (t *Compiler) compileRule(sc *ast.RuleStatement) (*base.RuleDefinition, error) {
164
	// Initialize a new base.RuleDefinition with the name and body from the rule statement.
165
	// The Arguments field is initialized as an empty map.
166
	ruleDefinition := &base.RuleDefinition{
167
		Name:      sc.Name.Literal,
168
		Arguments: map[string]base.AttributeType{},
169
	}
170
171
	var envOptions []cel.EnvOption
172
	envOptions = append(envOptions, cel.Variable("context", cel.DynType))
173
174
	// Iterate over the arguments in the rule statement.
175
	for name, ty := range sc.Arguments {
176
		// For each argument, use the getArgumentTypeIfExist function to determine the attribute type.
177
		typ, err := getArgumentTypeIfExist(ty)
178
		// If the attribute type is not recognized, return an error.
179
		if err != nil {
180
			return nil, err
181
		}
182
183
		cType, err := utils.GetCelType(typ)
184
		if err != nil {
185
			return nil, err
186
		}
187
188
		// Add the argument name and its corresponding attribute type to the Arguments map in the rule definition.
189
		ruleDefinition.Arguments[name.Literal] = typ
190
		envOptions = append(envOptions, cel.Variable(name.Literal, cType))
191
	}
192
193
	// Variables used within this expression environment.
194
	env, err := cel.NewEnv(envOptions...)
195
	if err != nil {
196
		return nil, err
197
	}
198
199
	// Compile and type-check the expression.
200
	compiledExp, issues := env.Compile(sc.Expression)
201
	if issues != nil && issues.Err() != nil {
202
		pi := sc.Name.PositionInfo
203
		pi.LinePosition++
204
		return nil, compileError(pi, issues.Err().Error())
205
	}
206
207
	if compiledExp.OutputType() != cel.BoolType {
208
		return nil, compileError(sc.Name.PositionInfo, fmt.Sprintf("rule expression must result in a boolean type not %s", compiledExp.OutputType().String()))
209
	}
210
211
	expr, err := cel.AstToCheckedExpr(compiledExp)
212
	if err != nil {
213
		return nil, err
214
	}
215
216
	ruleDefinition.Expression = expr
217
218
	// Return the completed rule definition and no error.
219
	return ruleDefinition, nil
220
}
221
222
// compileExpressionStatement compiles an ExpressionStatement into a Child node that can be used to construct an PermissionDefinition.
223
// It calls compileChildren to compile the expression into Child node(s).
224
// entityName is passed as an argument to the function to use it as a reference to the parent entity.
225
// Returns a pointer to a Child and an error if the compilation process fails.
226
func (t *Compiler) compileExpressionStatement(entityName string, expression *ast.ExpressionStatement) (*base.Child, error) {
227
	return t.compileChildren(entityName, expression.Expression)
228
}
229
230
// compileChildren - compiles the child nodes of an expression and returns a Child struct that represents them.
231
func (t *Compiler) compileChildren(entityName string, expression ast.Expression) (*base.Child, error) {
232
	if expression.IsInfix() {
233
		return t.compileRewrite(entityName, expression.(*ast.InfixExpression))
234
	}
235
	return t.compileLeaf(entityName, expression)
236
}
237
238
// compileRewrite - Compiles an InfixExpression node of type OR or AND to a base.Child struct with a base.Rewrite struct
239
// representing the logical operation of the expression. Recursively calls compileChildren to compile the child nodes.
240
// Parameters:
241
// - entityName: The name of the entity being compiled
242
// - exp: The InfixExpression node being compiled
243
// Returns:
244
// - *base.Child: A pointer to a base.Child struct representing the expression
245
// - error: An error if one occurred during compilation
246
func (t *Compiler) compileRewrite(entityName string, exp *ast.InfixExpression) (*base.Child, error) {
247
	var err error
248
249
	child := &base.Child{}
250
	rewrite := &base.Rewrite{}
251
252
	switch exp.Operator {
253
	case ast.OR:
254
		rewrite.RewriteOperation = base.Rewrite_OPERATION_UNION
255
	case ast.AND:
256
		rewrite.RewriteOperation = base.Rewrite_OPERATION_INTERSECTION
257
	case ast.NOT:
258
		rewrite.RewriteOperation = base.Rewrite_OPERATION_EXCLUSION
259
	default:
260
		rewrite.RewriteOperation = base.Rewrite_OPERATION_UNSPECIFIED
261
	}
262
263
	var ch []*base.Child
264
265
	var leftChild *base.Child
266
	leftChild, err = t.compileChildren(entityName, exp.Left)
267
	if err != nil {
268
		return nil, err
269
	}
270
271
	var rightChild *base.Child
272
	rightChild, err = t.compileChildren(entityName, exp.Right)
273
	if err != nil {
274
		return nil, err
275
	}
276
277
	ch = append(ch, []*base.Child{leftChild, rightChild}...)
278
279
	rewrite.Children = ch
280
	child.Type = &base.Child_Rewrite{Rewrite: rewrite}
281
	child.GetRewrite().Children = ch
282
	return child, nil
283
}
284
285
// compileLeaf is responsible for compiling a given AST (Abstract Syntax Tree) expression into a base.Child node.
286
// It does this based on the type of the provided expression.
287
// It expects either an identifier (a variable, a constant, etc.) or a function call.
288
// If the expression is neither of these types, it returns an error indicating that the relation definition was not found.
289
func (t *Compiler) compileLeaf(entityName string, expression ast.Expression) (*base.Child, error) {
290
	// Switch on the type of the expression.
291
	switch expression.GetType() {
292
293
	// Case when the expression is an Identifier (a variable, a constant, etc.).
294
	case ast.IDENTIFIER:
295
		// Type assertion to get the underlying Identifier.
296
		ident := expression.(*ast.Identifier)
297
298
		// Compile the identifier and return the result.
299
		return t.compileIdentifier(entityName, ident)
300
301
	// Case when the expression is a Call (a function call).
302
	case ast.CALL:
303
		// Type assertion to get the underlying Call.
304
		call := expression.(*ast.Call)
305
306
		// Compile the call and return the result.
307
		return t.compileCall(entityName, call)
308
309
	// Default case when the expression type is neither an Identifier nor a Call.
310
	default:
311
		// Return a nil Child and an error indicating that the relation definition was not found.
312
		return nil, compileError(token.PositionInfo{}, base.ErrorCode_ERROR_CODE_RELATION_DEFINITION_NOT_FOUND.String())
313
	}
314
}
315
316
// compileIdentifier compiles an ast.Identifier into a base.Child object.
317
// Depending on the length of the identifier and its type, it returns different types of Child object.
318
func (t *Compiler) compileIdentifier(entityName string, ident *ast.Identifier) (*base.Child, error) {
319
	// Initialize a new base.Child
320
	child := &base.Child{}
321
322
	// If the identifier has no segments, return an error
323
	if len(ident.Idents) == 0 {
324
		return nil, compileError(token.PositionInfo{
325
			LinePosition:   1,
326
			ColumnPosition: 1,
327
		}, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
328
	}
329
330
	// If the identifier has one segment
331
	if len(ident.Idents) == 1 {
332
		// Check the type of the reference from the schema
333
		typ, exist := t.schema.GetReferences().GetReferenceType(utils.Key(entityName, ident.Idents[0].Literal))
334
335
		// If reference validation is enabled and the reference does not exist, return an error
336
		if t.withReferenceValidation {
337
			if !exist {
338
				return nil, compileError(ident.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_UNDEFINED_RELATION_REFERENCE.String())
339
			}
340
		}
341
342
		// If the reference type is an attribute
343
		if typ == ast.ATTRIBUTE {
344
			// Get the attribute reference type from the schema
345
			at, exist := t.schema.GetReferences().GetAttributeReferenceTypeIfExist(utils.Key(entityName, ident.Idents[0].Literal))
346
			// If the attribute reference type does not exist or is not boolean, return an error
347
			if !exist || at.String() != "boolean" {
348
				return nil, compileError(ident.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
349
			}
350
351
			// Compile the identifier into a ComputedAttributeIdentifier
352
			leaf, err := t.compileComputedAttributeIdentifier(ident.Idents[0].Literal)
353
			if err != nil {
354
				return nil, compileError(ident.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
355
			}
356
357
			// Set the Type of the Child to the compiled Leaf
358
			child.Type = &base.Child_Leaf{Leaf: leaf}
359
			return child, nil
360
		} else { // The reference type is a user set
361
			// Compile the identifier into a ComputedUserSetIdentifier
362
			leaf, err := t.compileComputedUserSetIdentifier(ident.Idents[0].Literal)
363
			if err != nil {
364
				return nil, compileError(ident.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
365
			}
366
367
			// Set the Type of the Child to the compiled Leaf
368
			child.Type = &base.Child_Leaf{Leaf: leaf}
369
			return child, nil
370
		}
371
	}
372
373
	// If the identifier has two segments
374
	if len(ident.Idents) == 2 {
375
		// If reference validation is enabled, validate the tuple to user set reference
376
		if t.withReferenceValidation {
377
			err := t.validateTupleToUserSetReference(entityName, ident)
378
			if err != nil {
379
				return nil, err
380
			}
381
		}
382
383
		// Compile the identifier into a TupleToUserSetIdentifier
384
		leaf, err := t.compileTupleToUserSetIdentifier(ident.Idents[0].Literal, ident.Idents[1].Literal)
385
		if err != nil {
386
			return nil, compileError(ident.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
387
		}
388
389
		// Set the Type of the Child to the compiled Leaf
390
		child.Type = &base.Child_Leaf{Leaf: leaf}
391
		return child, nil
392
	}
393
394
	// If the identifier has more than two segments, return an error
395
	return nil, compileError(ident.Idents[2].PositionInfo, base.ErrorCode_ERROR_CODE_NOT_SUPPORTED_RELATION_WALK.String())
396
}
397
398
// compileCall compiles a function call within the Compiler.
399
// It takes the entityName and a pointer to an ast.Call object representing the function call.
400
// It returns a pointer to a base.Child object and an error, if any.
401
func (t *Compiler) compileCall(entityName string, call *ast.Call) (*base.Child, error) {
402
	// Create a new base.Child to store the compiled information for the call.
403
	child := &base.Child{}
404
405
	// Create a slice to store the call arguments.
406
	var arguments []*base.Argument
407
408
	// Create a map to store the types of the rule arguments, only if reference validation is enabled.
409
	var types map[string]string
410
411
	// If reference validation is enabled, try to get the rule argument types from the schema for the specific call.
412
	// If the call's rule does not exist in the schema, return an error.
413
	if t.withReferenceValidation {
414
		var exist bool
415
		types, exist = t.schema.GetReferences().GetRuleArgumentTypesIfRuleExist(call.Name.Literal)
416
		if !exist {
417
			return nil, compileError(call.Name.PositionInfo, base.ErrorCode_ERROR_CODE_INVALID_RULE_REFERENCE.String())
418
		}
419
420
		if len(types) != len(call.Arguments) {
421
			return nil, compileError(call.Name.PositionInfo, base.ErrorCode_ERROR_CODE_MISSING_ARGUMENT.String())
422
		}
423
	}
424
425
	if len(call.Arguments) == 0 {
426
		return nil, compileError(call.Name.PositionInfo, base.ErrorCode_ERROR_CODE_MISSING_ARGUMENT.String())
427
	}
428
429
	// Loop through each argument in the call.
430
	for _, argument := range call.Arguments {
431
432
		// Check if the argument has no identifiers, which is not allowed.
433
		// Return an error if this is the case.
434
		if len(argument.Idents) == 0 {
435
			return nil, compileError(token.PositionInfo{
436
				LinePosition:   1,
437
				ColumnPosition: 1,
438
			}, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
439
		}
440
441
		// If the argument has only one identifier, it is a computed attribute.
442
		if len(argument.Idents) == 1 {
443
444
			// If reference validation is enabled, check if the attribute reference exists and its type matches the rule's argument type.
445
			if t.withReferenceValidation {
446
				atyp, exist := t.schema.GetReferences().GetAttributeReferenceTypeIfExist(utils.Key(entityName, argument.Idents[0].Literal))
447
				if !exist {
448
					return nil, compileError(call.Name.PositionInfo, base.ErrorCode_ERROR_CODE_INVALID_RULE_REFERENCE.String())
449
				}
450
451
				// Get the type of the rule argument from the types map and compare it with the attribute reference type.
452
				typeInfo, exist := types[argument.Idents[0].Literal]
453
				if !exist {
454
					return nil, compileError(argument.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_INVALID_ARGUMENT.String())
455
				}
456
457
				if typeInfo != atyp.String() {
458
					return nil, compileError(argument.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_INVALID_ARGUMENT.String())
459
				}
460
			}
461
462
			// Append the computed attribute to the arguments slice.
463
			arguments = append(arguments, &base.Argument{
464
				Type: &base.Argument_ComputedAttribute{
465
					ComputedAttribute: &base.ComputedAttribute{
466
						Name: argument.Idents[0].Literal,
467
					},
468
				},
469
			})
470
			continue
471
		}
472
473
		// If the argument has more than two identifiers, it indicates an unsupported relation walk.
474
		// Return an error in this case.
475
		return nil, compileError(argument.Idents[1].PositionInfo, base.ErrorCode_ERROR_CODE_NOT_SUPPORTED_WALK.String())
476
	}
477
478
	// Set the child's type to be a leaf with the compiled call information.
479
	child.Type = &base.Child_Leaf{Leaf: &base.Leaf{
480
		Type: &base.Leaf_Call{Call: &base.Call{
481
			RuleName:  call.Name.Literal,
482
			Arguments: arguments,
483
		}},
484
	}}
485
486
	// Return the compiled child and nil error to indicate success.
487
	return child, nil
488
}
489
490
// compileComputedUserSetIdentifier takes a string that represents a user set relation
491
// and compiles it into a base.Leaf object containing that relation. It returns the resulting Leaf and no error.
492
func (t *Compiler) compileComputedUserSetIdentifier(r string) (l *base.Leaf, err error) {
493
	// Initialize a new base.Leaf
494
	leaf := &base.Leaf{}
495
496
	// Initialize a new base.ComputedUserSet with the provided relation
497
	computedUserSet := &base.ComputedUserSet{
498
		Relation: r,
499
	}
500
501
	// Set the Type of the Leaf to the newly created ComputedUserSet
502
	leaf.Type = &base.Leaf_ComputedUserSet{ComputedUserSet: computedUserSet}
503
504
	// Return the Leaf and no error
505
	return leaf, nil
506
}
507
508
// compileComputedAttributeIdentifier compiles a string that represents a computed attribute
509
// into a base.Leaf object containing that attribute. It returns the resulting Leaf and no error.
510
func (t *Compiler) compileComputedAttributeIdentifier(r string) (l *base.Leaf, err error) {
511
	// Initialize a new base.Leaf
512
	leaf := &base.Leaf{}
513
514
	// Initialize a new base.ComputedAttribute with the provided name
515
	computedAttribute := &base.ComputedAttribute{
516
		Name: r,
517
	}
518
519
	// Set the Type of the Leaf to the newly created ComputedAttribute
520
	leaf.Type = &base.Leaf_ComputedAttribute{ComputedAttribute: computedAttribute}
521
522
	// Return the Leaf and no error
523
	return leaf, nil
524
}
525
526
// compileTupleToUserSetIdentifier compiles a tuple to user set identifier to a leaf node in the IR tree.
527
// The resulting leaf node is used in the child node of an permission definition in the final compiled schema.
528
// It takes in the parameters p and r, which represent the parent and relation of the tuple, respectively.
529
// It returns a pointer to a leaf node and an error.
530
func (t *Compiler) compileTupleToUserSetIdentifier(p, r string) (l *base.Leaf, err error) {
531
	leaf := &base.Leaf{}
532
	computedUserSet := &base.ComputedUserSet{
533
		Relation: r,
534
	}
535
	tupleToUserSet := &base.TupleToUserSet{
536
		TupleSet: &base.TupleSet{
537
			Relation: p,
538
		},
539
		Computed: computedUserSet,
540
	}
541
	leaf.Type = &base.Leaf_TupleToUserSet{TupleToUserSet: tupleToUserSet}
542
	return leaf, nil
543
}
544
545
// validateReference checks if the provided identifier refers to a valid relation in the schema.
546
func (t *Compiler) validateTupleToUserSetReference(entityName string, identifier *ast.Identifier) error {
547
	// Stack to hold the types to be checked.
548
	typeCheckStack := make([]ast.RelationTypeStatement, 0)
549
550
	// Get initial relation types for the given entity.
551
	initialRelationTypes, doesExist := t.schema.GetReferences().GetRelationReferenceTypesIfExist(utils.Key(entityName, identifier.Idents[0].Literal))
552
	if !doesExist {
553
		// If initial relation does not exist, return an error.
554
		return compileError(identifier.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_UNDEFINED_RELATION_REFERENCE.String())
555
	}
556
557
	// Add the initial relation types to the stack.
558
	typeCheckStack = append(typeCheckStack, initialRelationTypes...)
559
560
	// While there are types to be checked in the stack...
561
	for len(typeCheckStack) > 0 {
562
		// Pop the last type from the stack.
563
		stackSize := len(typeCheckStack) - 1
564
		currentType := typeCheckStack[stackSize]
565
		typeCheckStack = typeCheckStack[:stackSize]
566
567
		if currentType.Relation.Literal == "" {
568
			typ, exist := t.schema.GetReferences().GetReferenceType(utils.Key(currentType.Type.Literal, identifier.Idents[1].Literal))
569
			// If the relation type does not exist, check if it is a valid relational reference.
570
			if !exist || typ == ast.ATTRIBUTE {
571
				// If not, return an error.
572
				return compileError(identifier.Idents[1].PositionInfo, base.ErrorCode_ERROR_CODE_UNDEFINED_RELATION_REFERENCE.String())
573
			}
574
		} else {
575
			// If the relation type does exist, get the corresponding relation types.
576
			relationTypes, doesExist := t.schema.GetReferences().GetRelationReferenceTypesIfExist(utils.Key(currentType.Type.Literal, currentType.Relation.Literal))
577
578
			if !doesExist {
579
				// If these types do not exist, return an error.
580
				return compileError(identifier.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_UNDEFINED_RELATION_REFERENCE.String())
581
			}
582
583
			// Add the newly found relation types to the stack.
584
			typeCheckStack = append(typeCheckStack, relationTypes...)
585
		}
586
	}
587
588
	// If the function didn't return until now, the reference is valid.
589
	return nil
590
}
591
592
// compileError creates an error with the given message and position information.
593
func compileError(info token.PositionInfo, message string) error {
594
	msg := fmt.Sprintf("%v:%v: %s", info.LinePosition, info.ColumnPosition, strings.ToLower(strings.Replace(strings.Replace(message, "ERROR_CODE_", "", -1), "_", " ", -1)))
595
	return errors.New(msg)
596
}
597
598
// getArgumentTypeIfExist takes a token and checks its literal value against
599
// the known attribute types ("string", "boolean", "integer", "float").
600
// If the literal value matches one of these types, it returns the corresponding base.AttributeType and no error.
601
// If the literal value does not match any of the known types, it returns an ATTRIBUTE_TYPE_UNSPECIFIED
602
// and an error indicating an invalid argument type.
603
func getArgumentTypeIfExist(tkn ast.AttributeTypeStatement) (base.AttributeType, error) {
604
	var attrType base.AttributeType
605
606
	switch tkn.Type.Literal {
607
	case "string":
608
		attrType = base.AttributeType_ATTRIBUTE_TYPE_STRING
609
	case "boolean":
610
		attrType = base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN
611
	case "integer":
612
		attrType = base.AttributeType_ATTRIBUTE_TYPE_INTEGER
613
	case "double":
614
		attrType = base.AttributeType_ATTRIBUTE_TYPE_DOUBLE
615
	default:
616
		return base.AttributeType_ATTRIBUTE_TYPE_UNSPECIFIED, compileError(tkn.Type.PositionInfo, base.ErrorCode_ERROR_CODE_INVALID_ARGUMENT.String())
617
	}
618
619
	if tkn.IsArray {
620
		return attrType + 1, nil
621
	}
622
623
	return attrType, nil
624
}
625