55using Postgrest . Exceptions ;
66using Postgrest . Extensions ;
77using Postgrest . Models ;
8+
89namespace Postgrest . Attributes
910{
10-
11- /// <summary>
12- /// Used to specify that a foreign key relationship exists in PostgreSQL
13- ///
14- /// See: https://postgrest.org/en/stable/api.html#resource-embedding
15- /// </summary>
16- [ AttributeUsage ( AttributeTargets . Property ) ]
17- public class ReferenceAttribute : Attribute
18- {
19- /// <summary>
20- /// Type of the model referenced
21- /// </summary>
22- public Type Model { get ; }
23-
24- /// <summary>
25- /// Associated property name
26- /// </summary>
27- public string PropertyName { get ; private set ; }
28-
29- /// <summary>
30- /// Table name of model
31- /// </summary>
32- public string TableName { get ; }
33-
34- /// <summary>
35- /// Columns that exist on the model we will select from.
36- /// </summary>
37- public List < string > Columns { get ; private set ; } = new ( ) ;
38-
39- /// <summary>
40- /// If the performed query is an Insert or Upsert, should this value be ignored? (DEFAULT TRUE)
41- /// </summary>
42- public bool IgnoreOnInsert { get ; private set ; }
43-
44- /// <summary>
45- /// If the performed query is an Update, should this value be ignored? (DEFAULT TRUE)
46- /// </summary>
47- public bool IgnoreOnUpdate { get ; private set ; }
48-
49- /// <summary>
50- /// If Reference should automatically be included in queries on this reference. (DEFAULT TRUE)
51- /// </summary>
52- public bool IncludeInQuery { get ; }
53-
54- /// <summary>
55- /// As to whether the query will filter top-level rows.
56- ///
57- /// See: https://postgrest.org/en/stable/api.html#resource-embedding
58- /// </summary>
59- public bool ShouldFilterTopLevel { get ; }
60-
61- /// <param name="model">Model referenced</param>
62- /// <param name="includeInQuery">Should referenced be included in queries?</param>
63- /// <param name="ignoreOnInsert">Should reference data be excluded from inserts/upserts?</param>
64- /// <param name="ignoreOnUpdate">Should reference data be excluded from updates?</param>
65- /// <param name="shouldFilterTopLevel">As to whether the query will filter top-level rows.</param>
66- /// <param name="propertyName"></param>
67- /// <exception cref="Exception"></exception>
68- public ReferenceAttribute ( Type model , bool includeInQuery = true , bool ignoreOnInsert = true , bool ignoreOnUpdate = true , bool shouldFilterTopLevel = true ,
69- [ CallerMemberName ] string propertyName = "" )
70- {
71- if ( ! IsDerivedFromBaseModel ( model ) )
72- {
73- throw new PostgrestException ( "ReferenceAttribute must be used with Postgrest BaseModels." ) { Reason = FailureHint . Reason . InvalidArgument } ;
74- }
75-
76- Model = model ;
77- IncludeInQuery = includeInQuery ;
78- IgnoreOnInsert = ignoreOnInsert ;
79- IgnoreOnUpdate = ignoreOnUpdate ;
80- PropertyName = propertyName ;
81- ShouldFilterTopLevel = shouldFilterTopLevel ;
82-
83- var attr = GetCustomAttribute ( model , typeof ( TableAttribute ) ) ;
84- TableName = attr is TableAttribute tableAttr ? tableAttr . Name : model . Name ;
85- }
86-
87- internal void ParseProperties ( List < ReferenceAttribute > ? seenRefs = null )
88- {
89- seenRefs ??= new List < ReferenceAttribute > ( ) ;
90-
91- ParseColumns ( ) ;
92- ParseRelationships ( seenRefs ) ;
93- }
94-
95- private void ParseColumns ( )
96- {
97- foreach ( var property in Model . GetProperties ( ) )
98- {
99- var attrs = property . GetCustomAttributes ( true ) ;
100-
101- foreach ( var item in attrs )
102- {
103- switch ( item )
104- {
105- case ColumnAttribute columnAttribute :
106- Columns . Add ( columnAttribute . ColumnName ) ;
107- break ;
108- case PrimaryKeyAttribute primaryKeyAttribute :
109- Columns . Add ( primaryKeyAttribute . ColumnName ) ;
110- break ;
111- }
112- }
113- }
114- }
115-
116- /// <inheritdoc />
117- public override bool Equals ( object obj )
118- {
119- if ( obj is ReferenceAttribute attribute )
120- {
121- return TableName == attribute . TableName && PropertyName == attribute . PropertyName && Model == attribute . Model ;
122- }
123-
124- return false ;
125- }
126-
127- private void ParseRelationships ( List < ReferenceAttribute > seenRefs )
128- {
129- foreach ( var property in Model . GetProperties ( ) )
130- {
131- var attrs = property . GetCustomAttributes ( true ) ;
132-
133- foreach ( var attr in attrs )
134- {
135- if ( attr is not ReferenceAttribute { IncludeInQuery : true } refAttr ) continue ;
136-
137- if ( seenRefs . FirstOrDefault ( r => r . Equals ( refAttr ) ) != null ) continue ;
138-
139- seenRefs . Add ( refAttr ) ;
140- refAttr . ParseProperties ( seenRefs ) ;
141-
142- Columns . Add ( ShouldFilterTopLevel ? $ "{ refAttr . TableName } !inner({ string . Join ( "," , refAttr . Columns . ToArray ( ) ) } )" : $ "{ refAttr . TableName } ({ string . Join ( "," , refAttr . Columns . ToArray ( ) ) } )") ;
143- }
144- }
145- }
146-
147- private static bool IsDerivedFromBaseModel ( Type type ) =>
148- type . GetInheritanceHierarchy ( ) . Any ( t => t == typeof ( BaseModel ) ) ;
149- }
150- }
11+ /// <summary>
12+ /// Used to specify that a foreign key relationship exists in PostgreSQL
13+ ///
14+ /// See: https://postgrest.org/en/stable/api.html#resource-embedding
15+ /// </summary>
16+ [ AttributeUsage ( AttributeTargets . Property ) ]
17+ public class ReferenceAttribute : Attribute
18+ {
19+ /// <summary>
20+ /// Specifies the Join type on this reference. PostgREST only allows for a LEFT join and an INNER join.
21+ /// </summary>
22+ public enum JoinType
23+ {
24+ /// <summary>
25+ /// INNER JOIN: returns rows when there is a match on both the source and the referenced tables.
26+ /// </summary>
27+ Inner ,
28+
29+ /// <summary>
30+ /// LEFT JOIN: returns all rows from the source table, even if there are no matches in the referenced table
31+ /// </summary>
32+ Left
33+ }
34+
35+ /// <summary>
36+ /// Type of the model referenced
37+ /// </summary>
38+ public Type Model { get ; }
39+
40+ /// <summary>
41+ /// Associated property name
42+ /// </summary>
43+ public string PropertyName { get ; private set ; }
44+
45+ /// <summary>
46+ /// Table name of model
47+ /// </summary>
48+ public string TableName { get ; }
49+
50+ /// <summary>
51+ /// Columns that exist on the model we will select from.
52+ /// </summary>
53+ public List < string > Columns { get ; private set ; } = new ( ) ;
54+
55+ /// <summary>
56+ /// If the performed query is an Insert or Upsert, should this value be ignored? (DEFAULT TRUE)
57+ /// </summary>
58+ public bool IgnoreOnInsert { get ; private set ; }
59+
60+ /// <summary>
61+ /// If the performed query is an Update, should this value be ignored? (DEFAULT TRUE)
62+ /// </summary>
63+ public bool IgnoreOnUpdate { get ; private set ; }
64+
65+ /// <summary>
66+ /// If Reference should automatically be included in queries on this reference. (DEFAULT TRUE)
67+ /// </summary>
68+ public bool IncludeInQuery { get ; }
69+
70+ /// <summary>
71+ /// As to whether the query will filter top-level rows.
72+ ///
73+ /// See: https://postgrest.org/en/stable/api.html#resource-embedding
74+ /// </summary>
75+ public bool UseInnerJoin { get ; }
76+
77+ /// <summary>Establishes a reference between two tables</summary>
78+ /// <param name="model">Model referenced</param>
79+ /// <param name="includeInQuery">Should referenced be included in queries?</param>
80+ /// <param name="ignoreOnInsert">Should reference data be excluded from inserts/upserts?</param>
81+ /// <param name="ignoreOnUpdate">Should reference data be excluded from updates?</param>
82+ /// <param name="joinType">Specifies the join type for this relationship</param>
83+ /// <param name="propertyName"></param>
84+ /// <exception cref="Exception"></exception>
85+ public ReferenceAttribute ( Type model , JoinType joinType , bool includeInQuery = true , bool ignoreOnInsert = true ,
86+ bool ignoreOnUpdate = true , [ CallerMemberName ] string propertyName = "" )
87+ : this ( model , includeInQuery , ignoreOnInsert , ignoreOnUpdate , joinType == JoinType . Inner , propertyName )
88+ {
89+ }
90+
91+ /// <summary>Establishes a reference between two tables</summary>
92+ /// <param name="model">Model referenced</param>
93+ /// <param name="includeInQuery">Should referenced be included in queries?</param>
94+ /// <param name="ignoreOnInsert">Should reference data be excluded from inserts/upserts?</param>
95+ /// <param name="ignoreOnUpdate">Should reference data be excluded from updates?</param>
96+ /// <param name="useInnerJoin">As to whether the query will filter top-level rows.</param>
97+ /// <param name="propertyName"></param>
98+ /// <exception cref="Exception"></exception>
99+ public ReferenceAttribute ( Type model , bool includeInQuery = true , bool ignoreOnInsert = true ,
100+ bool ignoreOnUpdate = true , bool useInnerJoin = true ,
101+ [ CallerMemberName ] string propertyName = "" )
102+ {
103+ if ( ! IsDerivedFromBaseModel ( model ) )
104+ throw new PostgrestException ( "ReferenceAttribute must be used with Postgrest BaseModels." )
105+ { Reason = FailureHint . Reason . InvalidArgument } ;
106+
107+ Model = model ;
108+ IncludeInQuery = includeInQuery ;
109+ IgnoreOnInsert = ignoreOnInsert ;
110+ IgnoreOnUpdate = ignoreOnUpdate ;
111+ PropertyName = propertyName ;
112+ UseInnerJoin = useInnerJoin ;
113+
114+ var attr = GetCustomAttribute ( model , typeof ( TableAttribute ) ) ;
115+ TableName = attr is TableAttribute tableAttr ? tableAttr . Name : model . Name ;
116+ }
117+
118+ internal void ParseProperties ( List < ReferenceAttribute > ? seenRefs = null )
119+ {
120+ seenRefs ??= new List < ReferenceAttribute > ( ) ;
121+
122+ ParseColumns ( ) ;
123+ ParseRelationships ( seenRefs ) ;
124+ }
125+
126+ private void ParseColumns ( )
127+ {
128+ foreach ( var property in Model . GetProperties ( ) )
129+ {
130+ var attrs = property . GetCustomAttributes ( true ) ;
131+
132+ foreach ( var item in attrs )
133+ {
134+ switch ( item )
135+ {
136+ case ColumnAttribute columnAttribute :
137+ Columns . Add ( columnAttribute . ColumnName ) ;
138+ break ;
139+ case PrimaryKeyAttribute primaryKeyAttribute :
140+ Columns . Add ( primaryKeyAttribute . ColumnName ) ;
141+ break ;
142+ }
143+ }
144+ }
145+ }
146+
147+ /// <inheritdoc />
148+ public override bool Equals ( object obj )
149+ {
150+ if ( obj is ReferenceAttribute attribute )
151+ {
152+ return TableName == attribute . TableName && PropertyName == attribute . PropertyName &&
153+ Model == attribute . Model ;
154+ }
155+
156+ return false ;
157+ }
158+
159+
160+ private void ParseRelationships ( List < ReferenceAttribute > seenRefs )
161+ {
162+ foreach ( var property in Model . GetProperties ( ) )
163+ {
164+ var attrs = property . GetCustomAttributes ( true ) ;
165+
166+ foreach ( var attr in attrs )
167+ {
168+ if ( attr is not ReferenceAttribute { IncludeInQuery : true } refAttr ) continue ;
169+
170+ if ( seenRefs . FirstOrDefault ( r => r . Equals ( refAttr ) ) != null ) continue ;
171+
172+ seenRefs . Add ( refAttr ) ;
173+ refAttr . ParseProperties ( seenRefs ) ;
174+
175+ Columns . Add ( UseInnerJoin
176+ ? $ "{ refAttr . TableName } !inner({ string . Join ( "," , refAttr . Columns . ToArray ( ) ) } )"
177+ : $ "{ refAttr . TableName } ({ string . Join ( "," , refAttr . Columns . ToArray ( ) ) } )") ;
178+ }
179+ }
180+ }
181+
182+ private static bool IsDerivedFromBaseModel ( Type type ) =>
183+ type . GetInheritanceHierarchy ( ) . Any ( t => t == typeof ( BaseModel ) ) ;
184+ }
185+ }
0 commit comments