Coverage for src/fluree_py/types/query/select.py: 90%

39 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-02 03:03 +0000

1""" 

2FlureeQL Select Clause Grammar 

3 

4The select clause in FlureeQL determines the structure and content of query results. 

5It can be either a select object or a select array. 

6 

7This grammar defines the syntax for FlureeQL select clauses. The types defined below 

8in this file implement this grammar in Python. 

9 

10Grammar (EBNF): 

11``` ebnf 

12 (* Main select clause structure *) 

13 SelectClause = SelectObject | SelectArray 

14 

15 (* Select object maps logic variables to expressions *) 

16 SelectObject = "{" LogicVariable ":" SelectExpressionList "}" 

17 SelectExpressionList = [SelectExpression {"," SelectExpression}] 

18 

19 (* Select array contains variables or objects *) 

20 SelectArray = "[" SelectArrayElement {"," SelectArrayElement} "]" 

21 SelectArrayElement = LogicVariable | SelectObject 

22 

23 (* Expression types *) 

24 SelectExpression = Wildcard | Predicate | NodeObjectTemplate 

25 NodeObjectTemplate = "{" Predicate ":" SelectExpressionList "}" 

26 

27 (* Basic elements *) 

28 LogicVariable = "?" (letter | digit | "-" | "_") {letter | digit | "-" | "_"} 

29 Predicate = string 

30 Wildcard = "*" 

31 

32 (* Character sets *) 

33 letter = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" 

34 digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" 

35 string = '"' {character} '"' 

36 character = letter | digit | "-" | "_" | ":" | "." 

37``` 

38 

39Example Queries: 

40 # Select object - get name and all predicates of best friend 

41 - { "?s": [ "name", { "bestFriend": ["*"] } ] } 

42 

43 # Select array - get multiple variables and objects 

44 - ["?s", "?name", "?friend"] 

45 - [ { "?s": ["*"] }, { "?friend": ["*"] } ] 

46 

47 # Node object template - nested data structures 

48 - { "schema:address": ["*"] } # Get all address predicates 

49 - { "bestFriend": ["*"] } # Get all best friend predicates 

50 - { "bestFriend": [ { "address": ["*"] } ] } # Get address of best friend 

51 

52 # Logic variable examples 

53 - "?firstname" 

54 - "?first-name" 

55 - "?first_name" 

56 - "?address-1" 

57""" 

58 

59import re 

60from typing import Any, Dict, List, TypeAlias, TypeGuard, Union 

61 

62from fluree_py.types.common import Predicate, Wildcard 

63 

64# Regex pattern to match valid logic variables. 

65# Starts with ? and is followed by alphanumeric characters, hyphens, or underscores. 

66LOGIC_VARIABLE_PATTERN = re.compile(r"^\?[a-zA-Z0-9_-]+$") 

67 

68LogicVariable: TypeAlias = str 

69""" 

70A logic variable name in a FlureeQL query. 

71Logic variables are strings that begin with a question mark, ?, followed by 

72alphanumeric characters, hyphens, and underscores. They are used to bind 

73subjects to variables in the query. 

74 

75Example Queries: 

76 "?firstname" 

77 "?first-name" 

78 "?first_name" 

79 "?address-1" 

80""" 

81 

82 

83def is_logic_variable(var: str) -> TypeGuard[LogicVariable]: 

84 """ 

85 Type guard to check if a string is a valid logic variable. 

86 """ 

87 if not all(c.isprintable() for c in var): 

88 return False 

89 return LOGIC_VARIABLE_PATTERN.search(var) is not None 

90 

91 

92SelectExpression: TypeAlias = Union[Wildcard, Predicate, "NodeObjectTemplate"] 

93""" 

94A select expression in a FlureeQL query. 

95Select expressions define what data to include in the query results. 

96They can be: 

971. A predicate (e.g., "schema:name") - includes the value of that predicate 

982. The wildcard "*" - includes all predicates of the subject 

993. A node object template - traverses nested predicate values 

100 

101Example Queries: 

102 ["name", { "bestFriend": ["*"] }] 

103""" 

104 

105SelectExpressionList: TypeAlias = List[SelectExpression] 

106""" 

107A list of select expressions in a FlureeQL query. 

108Used in both select objects and node templates to specify multiple expressions. 

109 

110Example Queries: 

111 ["name", "*", { "bestFriend": ["*"] }] 

112""" 

113 

114NodeObjectTemplate: TypeAlias = Dict[Predicate, "SelectExpressionList"] 

115""" 

116A node object template in a FlureeQL query. 

117Node object templates define how to traverse nested predicate values. 

118They are objects where the keys are predicates, and the values are arrays of 

119select expressions. This allows for recursive querying of nested data structures. 

120 

121Example Queries: 

122 { "schema:address": ["*"] } 

123 

124 # Return an object that has all predicates for the node that "bestFriend" refers to 

125 { "bestFriend": ["*"] } 

126 

127 # Multi-level nested object 

128 { "bestFriend": [ { "address": ["*"] } ] } 

129""" 

130 

131 

132def is_node_object_template(var: Any) -> TypeGuard[NodeObjectTemplate]: 

133 """ 

134 Type guard to check if a value is a valid node object template. 

135 """ 

136 if not isinstance(var, dict): 

137 return False 

138 return all(isinstance(k, str) and isinstance(v, list) for (k, v) in var.items()) # type: ignore 

139 

140 

141SelectObject: TypeAlias = Dict[LogicVariable, SelectExpressionList] 

142""" 

143A select object in a FlureeQL query. 

144A select object maps logic variables to arrays of select expressions. 

145Each logic variable corresponds to a set of subjects, and for each subject, 

146a JSON object is constructed based on the select expressions. 

147 

148Example Queries: 

149 { "?s": [ "name", { "bestFriend": ["*"] } ] } 

150""" 

151 

152 

153def is_select_object(var: Any) -> TypeGuard[SelectObject]: 

154 """ 

155 Type guard to check if a value is a valid select object. 

156 """ 

157 if not isinstance(var, dict): 

158 return False 

159 return all(is_logic_variable(k) and isinstance(v, list) for k, v in var.items()) # type: ignore 

160 

161 

162SelectArrayElement: TypeAlias = Union[LogicVariable, SelectObject] 

163""" 

164An element in a select array in a FlureeQL query. 

165An element in a select array can be either a logic variable or a select object. 

166 

167Example Queries: 

168 "?s" 

169 { "?s": ["*"] } 

170""" 

171 

172 

173def is_select_array_element(var: Any) -> TypeGuard[SelectArrayElement]: 

174 """ 

175 Type guard to check if a value is a valid select array element. 

176 """ 

177 return is_logic_variable(var) if isinstance(var, str) else is_select_object(var) 

178 

179 

180SelectArray: TypeAlias = List[SelectArrayElement] 

181""" 

182A select array in a FlureeQL query. 

183A select array is a list containing logic variables or select objects. 

184When using a select array, each element of the query results will be an array 

185containing the values for each element in the select array. 

186 

187Example Queries: 

188 ["?s", "?name", "?friend"] 

189 [ { "?s": ["*"] }, { "?friend": ["*"] } ] 

190""" 

191 

192 

193def is_select_array(var: Any) -> TypeGuard[SelectArray]: 

194 """ 

195 Type guard to check if a value is a valid select array. 

196 """ 

197 if not isinstance(var, list): 

198 return False 

199 

200 return all(is_select_array_element(v) for v in var) # type: ignore 

201 

202 

203SelectClause: TypeAlias = Union[SelectObject, SelectArray] 

204""" 

205A select clause in a FlureeQL query. 

206A select clause can be either a select object or a select array. 

207 

208Example Queries: 

209 - { "?s": [ "name", { "bestFriend": ["*"] } ] } 

210 - ["?s", "?name", "?friend"] 

211""" 

212 

213__all__ = [ 

214 "LogicVariable", 

215 "Predicate", 

216 "Wildcard", 

217 "SelectExpression", 

218 "SelectExpressionList", 

219 "NodeObjectTemplate", 

220 "SelectObject", 

221 "SelectArrayElement", 

222 "SelectArray", 

223 "is_logic_variable", 

224 "is_node_object_template", 

225 "is_select_object", 

226 "is_select_array_element", 

227 "is_select_array", 

228]