rails_age 0.6.2 → 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: '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