rails_age 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e8bae6566d3be3dc932bc834d67b969ddab46efb5bc72dfde5b245a861f9c09f
4
- data.tar.gz: 04ab7c0d236560abefbe9d637a24e5e027ada6a2f3c0279d4aace865262c5808
3
+ metadata.gz: '0856a8b6ebd00d824cb655f76712731b5f1034250520ddf90008099863ce70ff'
4
+ data.tar.gz: 38bfd628af67308dc59ec42eb3c7e89e6f2391c9bc4d84fd27fd24dfba89a4cd
5
5
  SHA512:
6
- metadata.gz: 6e2b16c1934dbdf79dce10028ee5d6066e93416a0ea2a31f378b2239db72383b11dec44a892a254721c75130f2b0fad5c4ab4a4dda0bdbf747ecb66bc695c8b7
7
- data.tar.gz: 5883fcd0e9a36c965683ec01702b9a8e6b8d31e66d991ef2331c21ab11aa49c894965f666937d456dbd5fc7aaea1b35577f9f9c9b8115c4775a83bf18eed4354
6
+ metadata.gz: c7f10fc55865bda5020d850470e103b1e8172e3b6fcf1be8a9fe7ecbac2b8617326cdbd3048bf241b5f5fe5a72a414d67f1f58bc2d6d06df4faa7d426ab49e86
7
+ data.tar.gz: ba30bcf0dabd7a66e68038d0314379663e1a6058f2b433947d961588cd593b3c318f3eec81ef7691d007029efbafa64d3cc2ba7fe8d92b56f9d51b31a7cf8d7b
data/CHANGELOG.md CHANGED
@@ -35,10 +35,9 @@ breaking change?: namespaces (by default) will use their own schema? (add to dat
35
35
  - **Age Path** - nodes and edges combined
36
36
  * add `rails generate apache_age:path_scaffold HasJob employee_role start_node:person end_node:company`
37
37
 
38
- ## VERSION 0.6.4 - 2024-xx-xx
38
+ ## VERSION 0.6.4 - 2024-10-30
39
39
 
40
40
  - **Query Sanitize**:
41
- * reject attributes not defined in model (throw error?)
42
41
  * allow and sanitize query strings with multiple attributes, ie: `Person.where("find.first_name = ? AND find.last_name = ?", 'John', 'Doe')`
43
42
 
44
43
  ## VERSION 0.6.3 - 2024-10-27
@@ -29,6 +29,7 @@ module ApacheAge
29
29
  @graph_name = graph_name || model_class.age_graph
30
30
  end
31
31
 
32
+ # TODO: allow for multiple graphs
32
33
  # def cypher(graph_name = 'age_schema')
33
34
  # return self if graph_name.blank?
34
35
 
@@ -41,38 +42,22 @@ module ApacheAge
41
42
  self
42
43
  end
43
44
 
44
- # # TODO: need to handle string inputs too: instead of: \
45
- # # "id(find) = #{id}" & "find.name = #{name}"
46
- # # we can have: "id(find) = ?", id & "find.name = ?", name
47
- # # ActiveRecord::Base.sanitize_sql([query_string, v])
48
45
  def where(*args)
49
46
  return self if args.blank?
50
47
 
51
48
  @where_clauses <<
52
- # not able to sanitize the query string in this case
53
- # ["first_name = 'Barney'"]
49
+ # not able to sanitize the query string in this case: `["first_name = 'Barney'"]`
54
50
  if args.length == 1 && args.first.is_a?(String)
55
- string_query = args.first
56
- if string_query.include?('id = ?')
57
- "id(find) = ?"
58
- elsif string_query.include?('id(') || string_query.include?('find.')
59
- string_query
60
- else
61
- "find.#{string_query}"
62
- end
51
+ raw_query_string = args.first
52
+ transform_cypher_sql(raw_query_string)
63
53
 
64
54
  # Handling & sanitizing parameterized string queries
65
55
  elsif args.length > 1 && args.first.is_a?(String)
66
56
  raw_query_string = args.first
67
- query_string =
68
- if raw_query_string.include?('id = ?')
69
- "id(find) = ?"
70
- elsif raw_query_string.include?('id(') || raw_query_string.include?('find.')
71
- raw_query_string
72
- else
73
- "find.#{raw_query_string}"
74
- end
57
+ # Replace `id = ?` with `id(find) = ?` and `first_name = ?` with `find.first_name = ?`
58
+ query_string = transform_cypher_sql(raw_query_string)
75
59
  values = args[1..-1]
60
+ # sanitize sql input values
76
61
  ActiveRecord::Base.sanitize_sql_array([query_string, *values])
77
62
 
78
63
  # Hashes are sanitized in the model class
@@ -93,71 +78,11 @@ module ApacheAge
93
78
  self
94
79
  end
95
80
 
96
- # # where is sanitized in the model class with hash values
97
- # def where(attributes)
98
- # return self if attributes.blank?
99
-
100
- # @where_clauses <<
101
- # if attributes.is_a?(String)
102
- # puts "HANDLE PURE STRING QUERIES"
103
- # if attributes.include?('id(') || attributes.include?('find.')
104
- # attributes
105
- # else
106
- # "find.#{attributes}"
107
- # end
108
- # else
109
- # puts "HANDLE HASHES"
110
- # pp attributes
111
- # edge_keys = [:start_id, :start_node, :end_id, :end_node]
112
- # if edge_keys.any? { |key| attributes.include?(key) }
113
- # puts "HANDLE EDGE CLAUSES"
114
- # model_class.send(:where_edge_clause, attributes)
115
- # else
116
- # puts "HANDLE NODE CLAUSES"
117
- # model_class.send(:where_node_clause, attributes)
118
- # end
119
- # end
120
-
121
- # self
122
- # end
123
-
124
- # # Pre-sanitize where statements
125
- # # def where(*args)
126
- # # return self if args.blank?
127
-
128
- # # # Handling parameterized query strings with values
129
- # # if args.length == 1 && args.first.is_a?(Hash)
130
- # # # If a hash of attributes is provided, use the existing logic
131
- # # attributes = args.first
132
- # # edge_keys = [:start_id, :start_node, :end_id, :end_node]
133
- # # if edge_keys.any? { |key| attributes.include?(key) }
134
- # # @where_clauses << model_class.send(:where_edge_clause, attributes)
135
- # # else
136
- # # @where_clauses << model_class.send(:where_node_clause, attributes)
137
- # # end
138
- # # elsif args.length > 1 && args.first.is_a?(String)
139
- # # # If a query string with placeholders and values is provided
140
- # # query_string = args.first
141
- # # values = args[1..-1]
142
- # # sanitized_query = ActiveRecord::Base.send(:sanitize_sql_array, [query_string, *values])
143
- # # @where_clauses << sanitized_query
144
- # # elsif args.length == 1 && args.first.is_a?(String)
145
- # # # If a single string is provided, use it directly (assuming it is already sanitized or trusted)
146
- # # @where_clauses << args.first
147
- # # else
148
- # # raise ArgumentError, "Invalid arguments for `where` method"
149
- # # end
150
-
151
- # # self
152
- # # end
153
-
154
81
  # New return method
155
82
  def return(*variables)
156
83
  return self if variables.blank?
157
84
 
158
85
  @return_variables = variables
159
- # @return_names = variables.empty? ? ['find'] : variables
160
- # @return_clause = variables.empty? ? 'find' : "find.#{variables.join(', find.')}"
161
86
  self
162
87
  end
163
88
 
@@ -243,22 +168,47 @@ module ApacheAge
243
168
  $$) AS (#{return_names.join(' agtype, ')} agtype);
244
169
  SQL
245
170
  end
246
- # def build_query(_extra_clause = nil)
247
- # sanitized_where_sql = where_clauses.any? ? "WHERE #{where_clauses.map { |clause| ActiveRecord::Base.sanitize_sql_like(clause) }.join(' AND ')}" : ''
248
- # sanitized_order_by = order_clause.present? ? ActiveRecord::Base.sanitize_sql_like(order_clause) : ''
249
- # sanitized_limit_clause = limit_clause.present? ? ActiveRecord::Base.sanitize_sql_like(limit_clause) : ''
250
171
 
251
- # <<-SQL.squish
252
- # SELECT *
253
- # FROM cypher('#{graph_name}', $$
254
- # MATCH #{ActiveRecord::Base.sanitize_sql_like(match_clause)}
255
- # #{sanitized_where_sql}
256
- # RETURN #{ActiveRecord::Base.sanitize_sql_like(return_clause)}
257
- # #{sanitized_order_by}
258
- # #{sanitized_limit_clause}
259
- # $$) AS (#{return_names.map { |name| "#{ActiveRecord::Base.sanitize_sql_like(name)} agtype" }.join(', ')});
260
- # SQL
261
- # end
172
+ def transform_cypher_sql(raw_sql_string)
173
+ # Define the logical operators and order multi-word operators first to avoid partial splits
174
+ operators = ['=', '>', '<', '<>', '>=', '<=', '=~', 'ENDS WITH', 'CONTAINS', 'STARTS WITH', 'IN', 'IS NULL', 'IS NOT NULL']
175
+ separators = ["AND NOT", "OR NOT", "AND", "OR", "NOT"]
176
+
177
+ # Combine the operators and separators into a regex pattern
178
+ pattern = /(#{(operators + separators).map { |s| Regexp.escape(s) }.join('|')})/
179
+
180
+ # Split the raw_sql_string string based on the pattern for operators and separators
181
+ parts = raw_sql_string.split(pattern)
182
+
183
+ # Process each part to identify and transform the attributes
184
+ transformed_parts = parts.map do |part|
185
+ # Skip transformation if part is one of the logical operators or separators
186
+ next part if operators.include?(part.strip) || separators.include?(part.strip)
187
+
188
+ if part.include?(".")
189
+ part # Keep parts with prefixes as they are
190
+ elsif part =~ /\s*(\w+)\s*$/
191
+ attribute = $1
192
+ if attribute == 'end_id'
193
+ "id(end_node)"
194
+ elsif attribute == 'start_id'
195
+ "id(start_node)"
196
+ elsif attribute == 'id'
197
+ "id(find)"
198
+ # attributes must start with a letter
199
+ elsif attribute =~ /^[a-z]\w*$/
200
+ "find.#{attribute}"
201
+ else
202
+ attribute
203
+ end
204
+ else
205
+ part
206
+ end
207
+ end
208
+
209
+ # Reassemble the string with the transformed parts
210
+ transformed_parts.join(" ").gsub(/\s+/, ' ').strip
211
+ end
262
212
  end
263
213
  end
264
214
  end
@@ -1,3 +1,3 @@
1
1
  module RailsAge
2
- VERSION = '0.6.3'
2
+ VERSION = '0.6.4'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_age
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bill Tihen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-27 00:00:00.000000000 Z
11
+ date: 2024-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails