rails_age 0.6.2 → 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: '01018a3efe8d4cef46fce365b6fd442882d8bf4ff51e0ad3d3bdb235db80ebb7'
4
- data.tar.gz: 69a20eb39f9609c6350463d6a391b7f08887079e51e56602a6357d914571ed8f
3
+ metadata.gz: '0856a8b6ebd00d824cb655f76712731b5f1034250520ddf90008099863ce70ff'
4
+ data.tar.gz: 38bfd628af67308dc59ec42eb3c7e89e6f2391c9bc4d84fd27fd24dfba89a4cd
5
5
  SHA512:
6
- metadata.gz: cc2ea56dcb102213397ca5ffc19edc095341e51307b0500d0be7bc7595cb65f64e74c6490a678703f5faf903d0396010b63b8dd33e162ed218e038a4e95e05f5
7
- data.tar.gz: 30e0472fb006bf883cb57a5c7406571d4fc4715a9d6d96e792dd5d3b4998e538eab74f809b5d384072d88e77ce2d6fb143cdb6a5c20fd044fd74673edd2bb13a
6
+ metadata.gz: c7f10fc55865bda5020d850470e103b1e8172e3b6fcf1be8a9fe7ecbac2b8617326cdbd3048bf241b5f5fe5a72a414d67f1f58bc2d6d06df4faa7d426ab49e86
7
+ data.tar.gz: ba30bcf0dabd7a66e68038d0314379663e1a6058f2b433947d961588cd593b3c318f3eec81ef7691d007029efbafa64d3cc2ba7fe8d92b56f9d51b31a7cf8d7b
data/CHANGELOG.md CHANGED
@@ -35,21 +35,21 @@ 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-10-30
38
39
 
39
- ## VERSION 0.6.3 - 2024-xx-xx
40
+ - **Query Sanitize**:
41
+ * allow and sanitize query strings with multiple attributes, ie: `Person.where("find.first_name = ? AND find.last_name = ?", 'John', 'Doe')`
42
+
43
+ ## VERSION 0.6.3 - 2024-10-27
40
44
 
41
45
  - **Query Sanitize**:
42
- * reject attributes not defined in model
43
46
  * sanitize strings using: id(find) = ?, 23 & find.first_name = ?, 'John'
47
+ NOTE: this sanitization only works (so far) for strings containing ONE attribute. ie: `Person.where("find.first_name = ?", 'John')` or `Person.where("first_name = ?", 'John')` works but `Person.where("find.first_name = ? AND find.last_name = ?", 'John', 'Doe')` does not yet work
44
48
 
45
49
  ## VERSION 0.6.2 - 2024-09-30
46
50
 
47
51
  - **Query Sanitize**
48
- * hashes sanitized
49
-
50
- - **TODO**:
51
- * reject attributes not defined in model
52
- * sanitize strings using: id(find) = ?, 23 & find.first_name = ?, 'John'
52
+ * hash queries sanitized
53
53
 
54
54
  ## VERSION 0.6.1 - 2024-09-29
55
55
 
@@ -8,9 +8,9 @@ module ApacheAge
8
8
  instance
9
9
  end
10
10
 
11
- def where(attributes)
11
+ def where(*attributes)
12
12
  query_builder = QueryBuilder.new(self)
13
- query_builder.where(attributes)
13
+ query_builder.where(*attributes)
14
14
  end
15
15
 
16
16
  def all = QueryBuilder.new(self).all
@@ -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,27 +42,37 @@ 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
- def where(attributes)
49
- return self if attributes.blank?
45
+ def where(*args)
46
+ return self if args.blank?
50
47
 
51
48
  @where_clauses <<
52
- if attributes.is_a?(String)
53
- if attributes.include?('id(') || attributes.include?('find.')
54
- attributes
55
- else
56
- "find.#{attributes}"
57
- end
58
- else
49
+ # not able to sanitize the query string in this case: `["first_name = 'Barney'"]`
50
+ if args.length == 1 && args.first.is_a?(String)
51
+ raw_query_string = args.first
52
+ transform_cypher_sql(raw_query_string)
53
+
54
+ # Handling & sanitizing parameterized string queries
55
+ elsif args.length > 1 && args.first.is_a?(String)
56
+ raw_query_string = args.first
57
+ # Replace `id = ?` with `id(find) = ?` and `first_name = ?` with `find.first_name = ?`
58
+ query_string = transform_cypher_sql(raw_query_string)
59
+ values = args[1..-1]
60
+ # sanitize sql input values
61
+ ActiveRecord::Base.sanitize_sql_array([query_string, *values])
62
+
63
+ # Hashes are sanitized in the model class
64
+ # [{:first_name=>"Barney", :last_name=>"Rubble", :gender=>"male"}]
65
+ elsif args.first.is_a?(Hash)
66
+ attributes = args.first
59
67
  edge_keys = [:start_id, :start_node, :end_id, :end_node]
60
68
  if edge_keys.any? { |key| attributes.include?(key) }
61
- model_class.send(:where_edge_clause, attributes)
69
+ model_class.send(:where_edge_clause, **attributes)
62
70
  else
63
- model_class.send(:where_node_clause, attributes)
71
+ model_class.send(:where_node_clause, **attributes)
64
72
  end
73
+
74
+ else
75
+ raise ArgumentError, "Invalid arguments for `where` method"
65
76
  end
66
77
 
67
78
  self
@@ -72,8 +83,6 @@ module ApacheAge
72
83
  return self if variables.blank?
73
84
 
74
85
  @return_variables = variables
75
- # @return_names = variables.empty? ? ['find'] : variables
76
- # @return_clause = variables.empty? ? 'find' : "find.#{variables.join(', find.')}"
77
86
  self
78
87
  end
79
88
 
@@ -159,22 +168,47 @@ module ApacheAge
159
168
  $$) AS (#{return_names.join(' agtype, ')} agtype);
160
169
  SQL
161
170
  end
162
- # def build_query(_extra_clause = nil)
163
- # sanitized_where_sql = where_clauses.any? ? "WHERE #{where_clauses.map { |clause| ActiveRecord::Base.sanitize_sql_like(clause) }.join(' AND ')}" : ''
164
- # sanitized_order_by = order_clause.present? ? ActiveRecord::Base.sanitize_sql_like(order_clause) : ''
165
- # sanitized_limit_clause = limit_clause.present? ? ActiveRecord::Base.sanitize_sql_like(limit_clause) : ''
166
-
167
- # <<-SQL.squish
168
- # SELECT *
169
- # FROM cypher('#{graph_name}', $$
170
- # MATCH #{ActiveRecord::Base.sanitize_sql_like(match_clause)}
171
- # #{sanitized_where_sql}
172
- # RETURN #{ActiveRecord::Base.sanitize_sql_like(return_clause)}
173
- # #{sanitized_order_by}
174
- # #{sanitized_limit_clause}
175
- # $$) AS (#{return_names.map { |name| "#{ActiveRecord::Base.sanitize_sql_like(name)} agtype" }.join(', ')});
176
- # SQL
177
- # end
171
+
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
178
212
  end
179
213
  end
180
214
  end
@@ -1,3 +1,3 @@
1
1
  module RailsAge
2
- VERSION = '0.6.2'
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.2
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-09-30 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