1
1
from __future__ import annotations
2
2
3
+ import base64
4
+ import json
5
+
3
6
from app .mdl .rewriter import Rewriter
4
7
from app .model import UnprocessableEntityError
5
8
from app .model .connector import Connector
6
9
7
- rules = ["column_is_valid" ]
10
+ rules = ["column_is_valid" , "relationship_is_valid" ]
8
11
9
12
10
13
class Validator :
11
14
def __init__ (self , connector : Connector , rewriter : Rewriter ):
12
15
self .connector = connector
13
16
self .rewriter = rewriter
14
17
15
- def validate (self , rule : str , parameters : dict [str , str ]):
18
+ def validate (self , rule : str , parameters : dict [str , str ], manifest_str : str ):
16
19
if rule not in rules :
17
20
raise RuleNotFoundError (rule )
18
21
try :
19
- getattr (self , f"_validate_{ rule } " )(parameters )
22
+ getattr (self , f"_validate_{ rule } " )(parameters , manifest_str )
20
23
except ValidationError as e :
21
24
raise e
22
25
except Exception as e :
23
26
raise ValidationError (f"Unknown exception: { type (e )} , message: { e !s} " )
24
27
25
- def _validate_column_is_valid (self , parameters : dict [str , str ]):
28
+ def _validate_column_is_valid (self , parameters : dict [str , str ], manifest_str : str ):
26
29
model_name = parameters .get ("modelName" )
27
30
column_name = parameters .get ("columnName" )
28
31
if model_name is None :
@@ -37,6 +40,116 @@ def _validate_column_is_valid(self, parameters: dict[str, str]):
37
40
except Exception as e :
38
41
raise ValidationError (f"Exception: { type (e )} , message: { e !s} " )
39
42
43
+ def _validate_relationship_is_valid (
44
+ self , parameters : dict [str , str ], manifest_str : str
45
+ ):
46
+ relationship_name = parameters .get ("relationshipName" )
47
+ if relationship_name is None :
48
+ raise MissingRequiredParameterError ("relationship" )
49
+ decoded_manifest = base64 .b64decode (manifest_str ).decode ("utf-8" )
50
+ manifest = json .loads (decoded_manifest )
51
+
52
+ relationship = list (
53
+ filter (lambda r : r ["name" ] == relationship_name , manifest ["relationships" ])
54
+ )
55
+
56
+ if len (relationship ) == 0 :
57
+ raise ValidationError (
58
+ f"Relationship { relationship_name } not found in manifest"
59
+ )
60
+
61
+ left_model = self ._get_model (manifest , relationship [0 ]["models" ][0 ])
62
+ right_model = self ._get_model (manifest , relationship [0 ]["models" ][1 ])
63
+ relationship_type = relationship [0 ]["joinType" ].lower ()
64
+ condition = relationship [0 ]["condition" ]
65
+ columns = condition .split ("=" )
66
+ left_column = columns [0 ].strip ().split ("." )[1 ]
67
+ right_column = columns [1 ].strip ().split ("." )[1 ]
68
+
69
+ def generate_column_is_unique_sql (model_name , column_name ):
70
+ return f'SELECT count(*) = count(distinct { column_name } ) AS result FROM "{ model_name } "'
71
+
72
+ def generate_is_exist_join_sql (
73
+ left_model , right_model , left_column , right_column
74
+ ):
75
+ return f'SELECT count(*) > 0 AS result FROM "{ left_model } " JOIN "{ right_model } " ON "{ left_model } "."{ left_column } " = "{ right_model } "."{ right_column } "'
76
+
77
+ def generate_sql_from_type (
78
+ relationship_type , left_model , right_model , left_column , right_column
79
+ ):
80
+ if relationship_type == "one_to_one" :
81
+ return f"""WITH
82
+ lefttable AS ({ generate_column_is_unique_sql (left_model , left_column )} ),
83
+ righttable AS ({ generate_column_is_unique_sql (right_model , right_column )} ),
84
+ joinexist AS ({ generate_is_exist_join_sql (left_model , right_model , left_column , right_column )} )
85
+ SELECT lefttable.result AND righttable.result AND joinexist.result result,
86
+ lefttable.result left_table_unique,
87
+ righttable.result right_table_unique,
88
+ joinexist.result is_related
89
+ FROM lefttable, righttable, joinexist"""
90
+ elif relationship_type == "many_to_one" :
91
+ return f"""WITH
92
+ righttable AS ({ generate_column_is_unique_sql (right_model , right_column )} ),
93
+ joinexist AS ({ generate_is_exist_join_sql (left_model , right_model , left_column , right_column )} )
94
+ SELECT righttable.result AND joinexist.result result,
95
+ righttable.result right_table_unique,
96
+ joinexist.result is_related
97
+ FROM righttable, joinexist"""
98
+ elif relationship_type == "one_to_many" :
99
+ return f"""WITH
100
+ lefttable AS ({ generate_column_is_unique_sql (left_model , left_column )} ),
101
+ joinexist AS ({ generate_is_exist_join_sql (left_model , right_model , left_column , right_column )} )
102
+ SELECT lefttable.result AND joinexist.result result,
103
+ lefttable.result left_table_unique,
104
+ joinexist.result is_related
105
+ FROM lefttable, joinexist"""
106
+ elif relationship_type == "many_to_many" :
107
+ return f"""WITH
108
+ joinexist AS ({ generate_is_exist_join_sql (left_model , right_model , left_column , right_column )} )
109
+ SELECT joinexist.result result,
110
+ joinexist.result is_related
111
+ FROM joinexist"""
112
+ else :
113
+ raise ValidationError (f"Unknown relationship type: { relationship_type } " )
114
+
115
+ def format_result (result ):
116
+ output = {}
117
+ output ["result" ] = str (result .get ("result" ).get (0 ))
118
+ output ["is_related" ] = str (result .get ("is_related" ).get (0 ))
119
+ if result .get ("left_table_unique" ) is not None :
120
+ output ["left_table_unique" ] = str (
121
+ result .get ("left_table_unique" ).get (0 )
122
+ )
123
+ if result .get ("right_table_unique" ) is not None :
124
+ output ["right_table_unique" ] = str (
125
+ result .get ("right_table_unique" ).get (0 )
126
+ )
127
+ return output
128
+
129
+ sql = generate_sql_from_type (
130
+ relationship_type ,
131
+ left_model ["name" ],
132
+ right_model ["name" ],
133
+ left_column ,
134
+ right_column ,
135
+ )
136
+ try :
137
+ rewritten_sql = self .rewriter .rewrite (sql )
138
+ result = self .connector .query (rewritten_sql , limit = 1 )
139
+ if not result .get ("result" ).get (0 ):
140
+ raise ValidationError (
141
+ f"Relationship { relationship_name } is not valid: { format_result (result )} "
142
+ )
143
+
144
+ except Exception as e :
145
+ raise ValidationError (f"Exception: { type (e )} , message: { e !s} " )
146
+
147
+ def _get_model (self , manifest , model_name ):
148
+ models = list (filter (lambda m : m ["name" ] == model_name , manifest ["models" ]))
149
+ if len (models ) == 0 :
150
+ raise ValidationError (f"Model { model_name } not found in manifest" )
151
+ return models [0 ]
152
+
40
153
41
154
class ValidationError (UnprocessableEntityError ):
42
155
pass
0 commit comments