rails_age 0.6.3 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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