rails_age 0.6.1 → 0.6.3
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 +4 -4
- data/CHANGELOG.md +18 -5
- data/README.md +1 -1
- data/lib/apache_age/entities/class_methods.rb +26 -16
- data/lib/apache_age/entities/common_methods.rb +5 -3
- data/lib/apache_age/entities/edge.rb +30 -7
- data/lib/apache_age/entities/entity.rb +21 -10
- data/lib/apache_age/entities/node.rb +27 -7
- data/lib/apache_age/entities/query_builder.rb +122 -17
- data/lib/rails_age/version.rb +1 -1
- metadata +2 -3
- data/lib/apache_age/entities/vertex.rb +0 -53
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: e8bae6566d3be3dc932bc834d67b969ddab46efb5bc72dfde5b245a861f9c09f
         | 
| 4 | 
            +
              data.tar.gz: 04ab7c0d236560abefbe9d637a24e5e027ada6a2f3c0279d4aace865262c5808
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 6e2b16c1934dbdf79dce10028ee5d6066e93416a0ea2a31f378b2239db72383b11dec44a892a254721c75130f2b0fad5c4ab4a4dda0bdbf747ecb66bc695c8b7
         | 
| 7 | 
            +
              data.tar.gz: 5883fcd0e9a36c965683ec01702b9a8e6b8d31e66d991ef2331c21ab11aa49c894965f666937d456dbd5fc7aaea1b35577f9f9c9b8115c4775a83bf18eed4354
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -22,22 +22,35 @@ | |
| 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. | 
| 38 | 
            +
            ## VERSION 0.6.4 - 2024-xx-xx
         | 
| 31 39 |  | 
| 32 | 
            -
             | 
| 40 | 
            +
            - **Query Sanitize**:
         | 
| 41 | 
            +
              * reject attributes not defined in model (throw error?)
         | 
| 42 | 
            +
              * allow and sanitize query strings with multiple attributes, ie: `Person.where("find.first_name = ? AND find.last_name = ?", 'John', 'Doe')`
         | 
| 33 43 |  | 
| 34 | 
            -
             | 
| 44 | 
            +
            ## VERSION 0.6.3 - 2024-10-27
         | 
| 35 45 |  | 
| 36 | 
            -
            - ** | 
| 46 | 
            +
            - **Query Sanitize**:
         | 
| 47 | 
            +
              * sanitize strings using: id(find) = ?, 23 & find.first_name = ?, 'John'
         | 
| 48 | 
            +
                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
         | 
| 37 49 |  | 
| 38 | 
            -
            ## VERSION 0.6.2 - 2024- | 
| 50 | 
            +
            ## VERSION 0.6.2 - 2024-09-30
         | 
| 39 51 |  | 
| 40 52 | 
             
            - **Query Sanitize**
         | 
| 53 | 
            +
              * hash queries sanitized
         | 
| 41 54 |  | 
| 42 55 | 
             
            ## VERSION 0.6.1 - 2024-09-29
         | 
| 43 56 |  | 
    
        data/README.md
    CHANGED
    
    
| @@ -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
         | 
| @@ -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 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 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) =  | 
| 39 | 
            -
                    where_start_id = start_id ? "id(start_node) =  | 
| 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 | 
            -
                         | 
| 120 | 
            +
                        attributes[:end_id]
         | 
| 119 121 | 
             
                      elsif attributes[:end_node].is_a?(Node)
         | 
| 120 | 
            -
                         | 
| 122 | 
            +
                        attributes[:end_node]&.id
         | 
| 121 123 | 
             
                      end
         | 
| 122 | 
            -
                    where_end_id = end_id ? "id(end_node) =  | 
| 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 | 
            -
                         | 
| 128 | 
            +
                        attributes[:start_id]
         | 
| 127 129 | 
             
                      elsif attributes[:start_node].is_a?(Node)
         | 
| 128 | 
            -
                         | 
| 130 | 
            +
                        attributes[:start_node]&.id
         | 
| 129 131 | 
             
                      end
         | 
| 130 | 
            -
                    where_start_id = start_id ? "id(start_node) =  | 
| 132 | 
            +
                    where_start_id = start_id ? ActiveRecord::Base.sanitize_sql(["id(start_node) = ?", start_id]) : nil
         | 
| 131 133 |  | 
| 132 134 | 
             
                    where_end_attrs =
         | 
| 133 | 
            -
                       | 
| 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 | 
            -
                       | 
| 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  | 
| 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  | 
| 66 | 
            -
             | 
| 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 | 
            -
                         | 
| 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:#{ | 
| 77 | 
            -
                          WHERE id(from_node) = #{ | 
| 78 | 
            -
                          CREATE (from_node)-[edge#{ | 
| 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 | 
            -
                     | 
| 88 | 
            -
                      age_properties.map  | 
| 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}) = #{ | 
| 94 | 
            -
                          SET #{ | 
| 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 = | 
| 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 = | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 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 #{ | 
| 61 | 
            -
                            WHERE #{ | 
| 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 | 
            -
                     | 
| 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}#{ | 
| 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 | 
            -
                     | 
| 42 | 
            -
                       | 
| 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}) = #{ | 
| 48 | 
            -
                          SET #{ | 
| 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,29 +41,116 @@ module ApacheAge | |
| 41 41 | 
             
                    self
         | 
| 42 42 | 
             
                  end
         | 
| 43 43 |  | 
| 44 | 
            -
                  # need to handle string inputs too:  | 
| 45 | 
            -
                   | 
| 46 | 
            -
             | 
| 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(*args)
         | 
| 49 | 
            +
                    return self if args.blank?
         | 
| 47 50 |  | 
| 48 51 | 
             
                    @where_clauses <<
         | 
| 49 | 
            -
                       | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            +
                      # not able to sanitize the query string in this case
         | 
| 53 | 
            +
                      # ["first_name = 'Barney'"]
         | 
| 54 | 
            +
                      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
         | 
| 52 60 | 
             
                        else
         | 
| 53 | 
            -
                          "find.#{ | 
| 61 | 
            +
                          "find.#{string_query}"
         | 
| 54 62 | 
             
                        end
         | 
| 55 | 
            -
             | 
| 63 | 
            +
             | 
| 64 | 
            +
                      # Handling & sanitizing parameterized string queries
         | 
| 65 | 
            +
                      elsif args.length > 1 && args.first.is_a?(String)
         | 
| 66 | 
            +
                        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
         | 
| 75 | 
            +
                        values = args[1..-1]
         | 
| 76 | 
            +
                        ActiveRecord::Base.sanitize_sql_array([query_string, *values])
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                      # Hashes are sanitized in the model class
         | 
| 79 | 
            +
                      # [{:first_name=>"Barney", :last_name=>"Rubble", :gender=>"male"}]
         | 
| 80 | 
            +
                      elsif args.first.is_a?(Hash)
         | 
| 81 | 
            +
                        attributes = args.first
         | 
| 56 82 | 
             
                        edge_keys = [:start_id, :start_node, :end_id, :end_node]
         | 
| 57 83 | 
             
                        if edge_keys.any? { |key| attributes.include?(key) }
         | 
| 58 | 
            -
                          model_class.send(:where_edge_clause, attributes)
         | 
| 84 | 
            +
                          model_class.send(:where_edge_clause, **attributes)
         | 
| 59 85 | 
             
                        else
         | 
| 60 | 
            -
                          model_class.send(:where_node_clause, attributes)
         | 
| 86 | 
            +
                          model_class.send(:where_node_clause, **attributes)
         | 
| 61 87 | 
             
                        end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                      else
         | 
| 90 | 
            +
                        raise ArgumentError, "Invalid arguments for `where` method"
         | 
| 62 91 | 
             
                      end
         | 
| 63 92 |  | 
| 64 93 | 
             
                    self
         | 
| 65 94 | 
             
                  end
         | 
| 66 95 |  | 
| 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 | 
            +
             | 
| 67 154 | 
             
                  # New return method
         | 
| 68 155 | 
             
                  def return(*variables)
         | 
| 69 156 | 
             
                    return self if variables.blank?
         | 
| @@ -112,21 +199,23 @@ module ApacheAge | |
| 112 199 |  | 
| 113 200 | 
             
                  private
         | 
| 114 201 |  | 
| 202 | 
            +
                  # TODO: ensure ordering keys are present in the model
         | 
| 115 203 | 
             
                  def parse_ordering(ordering)
         | 
| 116 204 | 
             
                    if ordering.is_a?(Hash)
         | 
| 117 | 
            -
                       | 
| 118 | 
            -
             | 
| 205 | 
            +
                      ordering =
         | 
| 206 | 
            +
                        ordering
         | 
| 207 | 
            +
                          .map { |k, v| "find.#{k} #{ActiveRecord::Base.sanitize_sql_like(v.to_s)}" }
         | 
| 208 | 
            +
                          .join(', ')
         | 
| 119 209 | 
             
                    elsif ordering.is_a?(Symbol)
         | 
| 120 | 
            -
                      # If it's a symbol, simply prepend "find."
         | 
| 121 210 | 
             
                      ordering = "find.#{ordering}"
         | 
| 122 211 | 
             
                    elsif ordering.is_a?(String)
         | 
| 123 | 
            -
                       | 
| 124 | 
            -
                      ordering = ordering
         | 
| 212 | 
            +
                      ordering
         | 
| 125 213 | 
             
                    elsif ordering.is_a?(Array)
         | 
| 126 | 
            -
                      # If it's an array, process each element recursively
         | 
| 127 214 | 
             
                      ordering = ordering.map do |order|
         | 
| 128 215 | 
             
                        if order.is_a?(Hash)
         | 
| 129 | 
            -
                          order | 
| 216 | 
            +
                          order
         | 
| 217 | 
            +
                            .map { |k, v| "find.#{k} #{ActiveRecord::Base.sanitize_sql_like(v.to_s)}" }
         | 
| 218 | 
            +
                            .join(', ')
         | 
| 130 219 | 
             
                        elsif order.is_a?(Symbol)
         | 
| 131 220 | 
             
                          "find.#{order}"
         | 
| 132 221 | 
             
                        elsif order.is_a?(String)
         | 
| @@ -154,6 +243,22 @@ module ApacheAge | |
| 154 243 | 
             
                      $$) AS (#{return_names.join(' agtype, ')} agtype);
         | 
| 155 244 | 
             
                    SQL
         | 
| 156 245 | 
             
                  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 | 
            +
             | 
| 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
         | 
| 157 262 | 
             
                end
         | 
| 158 263 | 
             
              end
         | 
| 159 264 | 
             
            end
         | 
    
        data/lib/rails_age/version.rb
    CHANGED
    
    
    
        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. | 
| 4 | 
            +
              version: 0.6.3
         | 
| 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- | 
| 11 | 
            +
            date: 2024-10-27 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
         |