rails_age 0.6.1 → 0.6.2

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: 053d21a51d2e91cb61ea1678d2d84c05ac4a1a84a643334c4993df9f6ca9c3c0
4
- data.tar.gz: 57f945d6055d9dca60414321e3f39dd09abfdb1a8f2a3a5fec0b4c5e3e9ee656
3
+ metadata.gz: '01018a3efe8d4cef46fce365b6fd442882d8bf4ff51e0ad3d3bdb235db80ebb7'
4
+ data.tar.gz: 69a20eb39f9609c6350463d6a391b7f08887079e51e56602a6357d914571ed8f
5
5
  SHA512:
6
- metadata.gz: 017c585f995749dad85f341b41a01d2f94b0cc03fac6240d63e4755e8ab0c7148969793fbe9c308f13c0ea94d35eee3fb231516eb775817a53bef003843d41e4
7
- data.tar.gz: 83760cc109544589665b7297776538f48f4ac0c14273455957e714ff6d9614e1a4e8d8ba862e39b8735a5f1e5a1f9c8ca5e429f1823aee8b98e38bad3dded2de
6
+ metadata.gz: cc2ea56dcb102213397ca5ffc19edc095341e51307b0500d0be7bc7595cb65f64e74c6490a678703f5faf903d0396010b63b8dd33e162ed218e038a4e95e05f5
7
+ data.tar.gz: 30e0472fb006bf883cb57a5c7406571d4fc4715a9d6d96e792dd5d3b4998e538eab74f809b5d384072d88e77ce2d6fb143cdb6a5c20fd044fd74673edd2bb13a
data/CHANGELOG.md CHANGED
@@ -22,22 +22,34 @@
22
22
  * paths support
23
23
  * select attributes support
24
24
 
25
+ ## VERSION 0.8.0 - 2024-xx-xx
26
+
27
+ breaking change?: namespaces (by default) will use their own schema? (add to database.yml & schema.rb ?)
28
+
29
+ - **AGE Schema override**
30
+
31
+ - **Multiple AGE Schema**
32
+
25
33
  ## VERSION 0.7.0 - 2024-xx-xx
26
34
 
27
35
  - **Age Path** - nodes and edges combined
28
36
  * add `rails generate apache_age:path_scaffold HasJob employee_role start_node:person end_node:company`
29
37
 
30
- ## VERSION 0.6.3 - 2024-xx-xx
31
-
32
- breaking change?: namespaces (by default) will use their own schema? (add to database.yml & schema.rb ?)
33
38
 
34
- - **AGE Schema override**
39
+ ## VERSION 0.6.3 - 2024-xx-xx
35
40
 
36
- - **multiple AGE Schema**
41
+ - **Query Sanitize**:
42
+ * reject attributes not defined in model
43
+ * sanitize strings using: id(find) = ?, 23 & find.first_name = ?, 'John'
37
44
 
38
- ## VERSION 0.6.2 - 2024-xx-xx
45
+ ## VERSION 0.6.2 - 2024-09-30
39
46
 
40
47
  - **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'
41
53
 
42
54
  ## VERSION 0.6.1 - 2024-09-29
43
55
 
data/README.md CHANGED
@@ -387,7 +387,7 @@ query =
387
387
  .execute
388
388
  ```
389
389
 
390
- or more generally:
390
+ or more generally (soon - not yet tested nor santized):
391
391
 
392
392
  ```ruby
393
393
  tihen =
@@ -25,18 +25,20 @@ module ApacheAge
25
25
 
26
26
  # Private stuff
27
27
 
28
+ # used? or dead code?
28
29
  def where_edge(attributes)
29
30
  where_attribs =
30
31
  attributes
31
- .compact
32
- .except(:end_id, :start_id, :end_node, :start_node)
33
- .map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
32
+ .compact
33
+ .except(:end_id, :start_id, :end_node, :start_node)
34
+ .map { |k, v| ActiveRecord::Base.sanitize_sql(["find.#{k} = ?", v]) }
35
+ .join(' AND ')
34
36
  where_attribs = where_attribs.empty? ? nil : where_attribs
35
37
 
36
38
  end_id = attributes[:end_id] || attributes[:end_node]&.id
37
39
  start_id = attributes[:start_id] || attributes[:start_node]&.id
38
- where_end_id = end_id ? "id(end_node) = #{end_id}" : nil
39
- where_start_id = start_id ? "id(start_node) = #{start_id}" : nil
40
+ where_end_id = end_id ? ActiveRecord::Base.sanitize_sql(["id(end_node) = ?", end_id]) : nil
41
+ where_start_id = start_id ? ActiveRecord::Base.sanitize_sql(["id(start_node) = ?", start_id]) : nil
40
42
 
41
43
  where_clause = [where_attribs, where_start_id, where_end_id].compact.join(' AND ')
42
44
  return nil if where_clause.empty?
@@ -115,33 +117,41 @@ module ApacheAge
115
117
 
116
118
  end_id =
117
119
  if attributes[:end_id]
118
- end_id = attributes[:end_id]
120
+ attributes[:end_id]
119
121
  elsif attributes[:end_node].is_a?(Node)
120
- end_id = attributes[:end_node]&.id
122
+ attributes[:end_node]&.id
121
123
  end
122
- where_end_id = end_id ? "id(end_node) = #{end_id}" : nil
124
+ where_end_id = end_id ? ActiveRecord::Base.sanitize_sql(["id(end_node) = ?", end_id]) : nil
123
125
 
124
126
  start_id =
125
127
  if attributes[:start_id]
126
- start_id = attributes[:start_id]
128
+ attributes[:start_id]
127
129
  elsif attributes[:start_node].is_a?(Node)
128
- start_id = attributes[:start_node]&.id
130
+ attributes[:start_node]&.id
129
131
  end
130
- where_start_id = start_id ? "id(start_node) = #{start_id}" : nil
132
+ where_start_id = start_id ? ActiveRecord::Base.sanitize_sql(["id(start_node) = ?", start_id]) : nil
131
133
 
132
134
  where_end_attrs =
133
- attributes[:end_node].map { |k, v| "end_node.#{k} = '#{v}'" } if attributes[:end_node].is_a?(Hash)
135
+ if attributes[:end_node].is_a?(Hash)
136
+ attributes[:end_node].map { |k, v| ActiveRecord::Base.sanitize_sql(["end_node.#{k} = ?", v]) }
137
+ end
134
138
  where_start_attrs =
135
- attributes[:start_node].map { |k, v| "start_node.#{k} = '#{v}'" } if attributes[:start_node].is_a?(Hash)
139
+ if attributes[:start_node].is_a?(Hash)
140
+ attributes[:start_node].map { |k, v| ActiveRecord::Base.sanitize_sql(["start_node.#{k} = ?", v]) }
141
+ end
136
142
 
137
143
  [core_clauses, where_start_id, where_end_id, where_start_attrs, where_end_attrs]
138
144
  .flatten.compact.join(' AND ')
139
145
  end
140
146
 
147
+
141
148
  def build_core_where_clause(attributes)
142
149
  attributes
143
150
  .compact
144
- .map { |k, v| k == :id ? "id(find) = #{v}" : "find.#{k} = '#{v}'"}
151
+ .map do |k, v|
152
+ query_string = k == :id ? "id(find) = #{v}" : "find.#{k} = '#{v}'"
153
+ ActiveRecord::Base.sanitize_sql([query_string, v])
154
+ end
145
155
  .join(' AND ')
146
156
  end
147
157
  end
@@ -57,13 +57,14 @@ module ApacheAge
57
57
  def destroy
58
58
  match_clause = (age_type == 'vertex' ? "(done:#{age_label})" : "()-[done:#{age_label}]->()")
59
59
  delete_clause = (age_type == 'vertex' ? 'DETACH DELETE done' : 'DELETE done')
60
+ sanitized_id = ActiveRecord::Base.sanitize_sql(["id(done) = ?", id])
60
61
  cypher_sql =
61
62
  <<-SQL
62
63
  SELECT *
63
64
  FROM cypher('#{age_graph}', $$
64
65
  MATCH #{match_clause}
65
- WHERE id(done) = #{id}
66
- #{delete_clause}
66
+ WHERE #{sanitized_id}
67
+ #{delete_clause}
67
68
  return done
68
69
  $$) as (deleted agtype);
69
70
  SQL
@@ -99,7 +100,8 @@ module ApacheAge
99
100
  def properties_to_s
100
101
  string_values =
101
102
  age_properties.each_with_object([]) do |(key, val), array|
102
- array << "#{key}: '#{val}'"
103
+ sanitized_val = ActiveRecord::Base.sanitize_sql(["?", val])
104
+ array << "#{key}: #{sanitized_val}"
103
105
  end
104
106
  "{#{string_values.join(', ')}}"
105
107
  end
@@ -70,12 +70,25 @@ module ApacheAge
70
70
  def create_sql
71
71
  self.start_node = start_node.save unless start_node.persisted?
72
72
  self.end_node = end_node.save unless end_node.persisted?
73
+
74
+ start_node_age_label = ActiveRecord::Base.sanitize_sql(start_node.age_label)
75
+ end_node_age_label = ActiveRecord::Base.sanitize_sql(end_node.age_label)
76
+ sanitized_start_id = ActiveRecord::Base.sanitize_sql(["?", start_node.id])
77
+ sanitized_end_id = ActiveRecord::Base.sanitize_sql(["?", end_node.id])
78
+ # cant use sanitize_sql_like because it escapes the % and _ characters
79
+ # label_name = ActiveRecord::Base.sanitize_sql_like(age_label)
80
+
81
+ reject_keys = %i[id start_id end_id start_node end_node]
82
+ sanitized_properties =
83
+ self.to_h.reject { |k, _v| reject_keys.include?(k) }.reject { |_k, v| v.nil? }
84
+ .map { |k, v| "#{k}: #{ActiveRecord::Base.sanitize_sql(["?", v])}" }
85
+ .join(', ')
73
86
  <<-SQL
74
87
  SELECT *
75
88
  FROM cypher('#{age_graph}', $$
76
- MATCH (from_node:#{start_node.age_label}), (to_node:#{end_node.age_label})
77
- WHERE id(from_node) = #{start_node.id} and id(to_node) = #{end_node.id}
78
- CREATE (from_node)-[edge#{self}]->(to_node)
89
+ MATCH (from_node:#{start_node_age_label}), (to_node:#{end_node_age_label})
90
+ WHERE id(from_node) = #{sanitized_start_id} AND id(to_node) = #{sanitized_end_id}
91
+ CREATE (from_node)-[edge:#{age_label} {#{sanitized_properties}}]->(to_node)
79
92
  RETURN edge
80
93
  $$) as (edge agtype);
81
94
  SQL
@@ -84,14 +97,24 @@ module ApacheAge
84
97
  # So far just properties of string type with '' around them
85
98
  def update_sql
86
99
  alias_name = age_alias || age_label.downcase
87
- set_caluse =
88
- age_properties.map { |k, v| v ? "#{alias_name}.#{k} = '#{v}'" : "#{alias_name}.#{k} = NULL" }.join(', ')
100
+ set_clause =
101
+ age_properties.map do |k, v|
102
+ if v
103
+ sanitized_value = ActiveRecord::Base.sanitize_sql(["?", v])
104
+ "#{alias_name}.#{k} = #{sanitized_value}"
105
+ else
106
+ "#{alias_name}.#{k} = NULL"
107
+ end
108
+ end.join(', ')
109
+
110
+ sanitized_id = ActiveRecord::Base.sanitize_sql(["?", id])
111
+
89
112
  <<-SQL
90
113
  SELECT *
91
114
  FROM cypher('#{age_graph}', $$
92
115
  MATCH ()-[#{alias_name}:#{age_label}]->()
93
- WHERE id(#{alias_name}) = #{id}
94
- SET #{set_caluse}
116
+ WHERE id(#{alias_name}) = #{sanitized_id}
117
+ SET #{set_clause}
95
118
  RETURN #{alias_name}
96
119
  $$) as (#{age_label} agtype);
97
120
  SQL
@@ -3,14 +3,20 @@ module ApacheAge
3
3
  class Entity
4
4
  class << self
5
5
  def find_by(attributes)
6
- where_clause = attributes.map { |k, v| "find.#{k} = '#{v}'" }.join(' AND ')
6
+ where_clause =
7
+ attributes
8
+ .map do |k, v|
9
+ if k == :id
10
+ ActiveRecord::Base.sanitize_sql(["id(find) = ?", v])
11
+ else
12
+ ActiveRecord::Base.sanitize_sql(["find.#{k} = ?", v])
13
+ end
14
+ end
15
+ .join(' AND ')
7
16
  handle_find(where_clause)
8
17
  end
9
18
 
10
- def find(id)
11
- where_clause = "id(find) = #{id}"
12
- handle_find(where_clause)
13
- end
19
+ def find(id) = find_by(id: id)
14
20
 
15
21
  private
16
22
 
@@ -46,19 +52,24 @@ module ApacheAge
46
52
  json_data = JSON.parse(json_string)
47
53
 
48
54
  age_label = json_data['label']
49
- attribs = json_data.except('label', 'properties')
50
- .merge(json_data['properties'])
51
- .symbolize_keys
55
+ attribs =
56
+ json_data
57
+ .except('label', 'properties')
58
+ .merge(json_data['properties'])
59
+ .symbolize_keys
52
60
 
53
61
  "#{json_data['label'].gsub('__', '::')}".constantize.new(**attribs)
54
62
  end
55
63
 
56
64
  def find_sql(match_clause, where_clause)
65
+ sanitized_match_clause = ActiveRecord::Base.sanitize_sql(match_clause)
66
+ sanitized_where_clause = where_clause # Already sanitized in `find_by` or `find`
67
+
57
68
  <<-SQL
58
69
  SELECT *
59
70
  FROM cypher('#{age_graph}', $$
60
- MATCH #{match_clause}
61
- WHERE #{where_clause}
71
+ MATCH #{sanitized_match_clause}
72
+ WHERE #{sanitized_where_clause}
62
73
  RETURN find
63
74
  $$) as (found agtype);
64
75
  SQL
@@ -25,11 +25,21 @@ module ApacheAge
25
25
  # RETURN company
26
26
  # $$) as (Company agtype);
27
27
  def create_sql
28
+ # can't use sanitiye without a solution for '_' in the alias name & label
29
+ # alias_name = ActiveRecord::Base.sanitize_sql_like(age_alias || age_label.downcase)
30
+ # label_name = ActiveRecord::Base.sanitize_sql_like(age_label)
31
+
28
32
  alias_name = age_alias || age_label.downcase
29
- <<-SQL
33
+ sanitized_properties =
34
+ self
35
+ .to_h.reject { |k, v| k == :id }.reject { |k, v| v.nil? }
36
+ .map { |k, v| "#{k}: #{ActiveRecord::Base.sanitize_sql(["?", v])}" }
37
+ .join(', ')
38
+
39
+ <<~SQL.squish
30
40
  SELECT *
31
41
  FROM cypher('#{age_graph}', $$
32
- CREATE (#{alias_name}#{self})
42
+ CREATE (#{alias_name}:#{age_label} {#{sanitized_properties}})
33
43
  RETURN #{alias_name}
34
44
  $$) as (#{age_label} agtype);
35
45
  SQL
@@ -37,19 +47,29 @@ module ApacheAge
37
47
 
38
48
  # So far just properties of string type with '' around them
39
49
  def update_sql
40
- alias_name = age_alias || age_label.downcase
41
- set_caluse =
42
- age_properties.map { |k, v| v ? "#{alias_name}.#{k} = '#{v}'" : "#{alias_name}.#{k} = NULL" }.join(', ')
50
+ alias_name = ActiveRecord::Base.sanitize_sql_like(age_alias || age_label.downcase)
51
+ sanitized_set_clause = age_properties.map do |k, v|
52
+ if v
53
+ sanitized_value = ActiveRecord::Base.sanitize_sql(["?", v])
54
+ "#{alias_name}.#{k} = #{sanitized_value}"
55
+ else
56
+ "#{alias_name}.#{k} = NULL"
57
+ end
58
+ end.join(', ')
59
+
60
+ sanitized_id = ActiveRecord::Base.sanitize_sql(["?", id])
61
+
43
62
  <<-SQL
44
63
  SELECT *
45
64
  FROM cypher('#{age_graph}', $$
46
65
  MATCH (#{alias_name}:#{age_label})
47
- WHERE id(#{alias_name}) = #{id}
48
- SET #{set_caluse}
66
+ WHERE id(#{alias_name}) = #{sanitized_id}
67
+ SET #{sanitized_set_clause}
49
68
  RETURN #{alias_name}
50
69
  $$) as (#{age_label} agtype);
51
70
  SQL
52
71
  end
72
+
53
73
  end
54
74
  end
55
75
  end
@@ -41,7 +41,10 @@ module ApacheAge
41
41
  self
42
42
  end
43
43
 
44
- # need to handle string inputs too: ie: "id(find) = #{id}"
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])
45
48
  def where(attributes)
46
49
  return self if attributes.blank?
47
50
 
@@ -112,21 +115,23 @@ module ApacheAge
112
115
 
113
116
  private
114
117
 
118
+ # TODO: ensure ordering keys are present in the model
115
119
  def parse_ordering(ordering)
116
120
  if ordering.is_a?(Hash)
117
- # Convert hash into "find.key direction" format and join with commas
118
- ordering = ordering.map { |k, v| "find.#{k} #{v}" }.join(', ')
121
+ ordering =
122
+ ordering
123
+ .map { |k, v| "find.#{k} #{ActiveRecord::Base.sanitize_sql_like(v.to_s)}" }
124
+ .join(', ')
119
125
  elsif ordering.is_a?(Symbol)
120
- # If it's a symbol, simply prepend "find."
121
126
  ordering = "find.#{ordering}"
122
127
  elsif ordering.is_a?(String)
123
- # If it's a string, assume it's already in the correct format
124
- ordering = ordering
128
+ ordering
125
129
  elsif ordering.is_a?(Array)
126
- # If it's an array, process each element recursively
127
130
  ordering = ordering.map do |order|
128
131
  if order.is_a?(Hash)
129
- order.map { |k, v| "find.#{k} #{v}" }.join(', ')
132
+ order
133
+ .map { |k, v| "find.#{k} #{ActiveRecord::Base.sanitize_sql_like(v.to_s)}" }
134
+ .join(', ')
130
135
  elsif order.is_a?(Symbol)
131
136
  "find.#{order}"
132
137
  elsif order.is_a?(String)
@@ -154,6 +159,22 @@ module ApacheAge
154
159
  $$) AS (#{return_names.join(' agtype, ')} agtype);
155
160
  SQL
156
161
  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
157
178
  end
158
179
  end
159
180
  end
@@ -1,3 +1,3 @@
1
1
  module RailsAge
2
- VERSION = '0.6.1'
2
+ VERSION = '0.6.2'
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.1
4
+ version: 0.6.2
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-29 00:00:00.000000000 Z
11
+ date: 2024-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -119,7 +119,6 @@ files:
119
119
  - lib/apache_age/entities/node.rb
120
120
  - lib/apache_age/entities/path.rb
121
121
  - lib/apache_age/entities/query_builder.rb
122
- - lib/apache_age/entities/vertex.rb
123
122
  - lib/apache_age/node.rb
124
123
  - lib/apache_age/path.rb
125
124
  - lib/apache_age/types/factory.rb
@@ -1,53 +0,0 @@
1
- # module ApacheAge
2
- # module Entities
3
- # module Vertex
4
- # extend ActiveSupport::Concern
5
-
6
- # included do
7
- # include ActiveModel::Model
8
- # include ActiveModel::Dirty
9
- # include ActiveModel::Attributes
10
-
11
- # attribute :id, :integer
12
-
13
- # extend ApacheAge::Entities::ClassMethods
14
- # include ApacheAge::Entities::CommonMethods
15
- # end
16
-
17
- # def age_type = 'vertex'
18
-
19
- # # AgeSchema::Nodes::Company.create(company_name: 'Bedrock Quarry')
20
- # # SELECT *
21
- # # FROM cypher('age_schema', $$
22
- # # CREATE (company:Company {company_name: 'Bedrock Quarry'})
23
- # # RETURN company
24
- # # $$) as (Company agtype);
25
- # def create_sql
26
- # alias_name = age_alias || age_label.downcase
27
- # <<-SQL
28
- # SELECT *
29
- # FROM cypher('#{age_graph}', $$
30
- # CREATE (#{alias_name}#{self})
31
- # RETURN #{alias_name}
32
- # $$) as (#{age_label} agtype);
33
- # SQL
34
- # end
35
-
36
- # # So far just properties of string type with '' around them
37
- # def update_sql
38
- # alias_name = age_alias || age_label.downcase
39
- # set_caluse =
40
- # age_properties.map { |k, v| v ? "#{alias_name}.#{k} = '#{v}'" : "#{alias_name}.#{k} = NULL" }.join(', ')
41
- # <<-SQL
42
- # SELECT *
43
- # FROM cypher('#{age_graph}', $$
44
- # MATCH (#{alias_name}:#{age_label})
45
- # WHERE id(#{alias_name}) = #{id}
46
- # SET #{set_caluse}
47
- # RETURN #{alias_name}
48
- # $$) as (#{age_label} agtype);
49
- # SQL
50
- # end
51
- # end
52
- # end
53
- # end