Merge pull request #46948 from olefriis/add-arel-node-for-simple-sql-construction
Add Arel functionality for "stitching together" SQL
This commit is contained in:
commit
b73fb939de
@ -1,3 +1,9 @@
|
|||||||
|
* Multiple `Arel::Nodes::SqlLiteral` nodes can now be added together to
|
||||||
|
form `Arel::Nodes::Fragments` nodes. This allows joining several pieces
|
||||||
|
of SQL.
|
||||||
|
|
||||||
|
*Matthew Draper*, *Ole Friis*
|
||||||
|
|
||||||
* `ActiveRecord::Base#signed_id` raises if called on a new record
|
* `ActiveRecord::Base#signed_id` raises if called on a new record
|
||||||
|
|
||||||
Previously it would return an ID that was not usable, since it was based on `id = nil`.
|
Previously it would return an ID that was not usable, since it was based on `id = nil`.
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
require "arel/nodes/insert_statement"
|
require "arel/nodes/insert_statement"
|
||||||
require "arel/nodes/update_statement"
|
require "arel/nodes/update_statement"
|
||||||
require "arel/nodes/bind_param"
|
require "arel/nodes/bind_param"
|
||||||
|
require "arel/nodes/fragments"
|
||||||
|
|
||||||
# terminal
|
# terminal
|
||||||
|
|
||||||
|
35
activerecord/lib/arel/nodes/fragments.rb
Normal file
35
activerecord/lib/arel/nodes/fragments.rb
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Arel # :nodoc: all
|
||||||
|
module Nodes
|
||||||
|
class Fragments < Arel::Nodes::Node
|
||||||
|
attr_reader :values
|
||||||
|
|
||||||
|
def initialize(values = [])
|
||||||
|
super()
|
||||||
|
@values = values
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize_copy(other)
|
||||||
|
super
|
||||||
|
@values = @values.clone
|
||||||
|
end
|
||||||
|
|
||||||
|
def hash
|
||||||
|
[@values].hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def +(other)
|
||||||
|
raise ArgumentError, "Expected Arel node" unless Arel.arel_node?(other)
|
||||||
|
|
||||||
|
self.class.new([*@values, other])
|
||||||
|
end
|
||||||
|
|
||||||
|
def eql?(other)
|
||||||
|
self.class == other.class &&
|
||||||
|
self.values == other.values
|
||||||
|
end
|
||||||
|
alias :== :eql?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -14,6 +14,12 @@ def encode_with(coder)
|
|||||||
|
|
||||||
def fetch_attribute
|
def fetch_attribute
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def +(other)
|
||||||
|
raise ArgumentError, "Expected Arel node" unless Arel.arel_node?(other)
|
||||||
|
|
||||||
|
Fragments.new([self, other])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -791,6 +791,10 @@ def visit_Array(o, collector)
|
|||||||
end
|
end
|
||||||
alias :visit_Set :visit_Array
|
alias :visit_Set :visit_Array
|
||||||
|
|
||||||
|
def visit_Arel_Nodes_Fragments(o, collector)
|
||||||
|
inject_join o.values, collector, " "
|
||||||
|
end
|
||||||
|
|
||||||
def quote(value)
|
def quote(value)
|
||||||
return value if Arel::Nodes::SqlLiteral === value
|
return value if Arel::Nodes::SqlLiteral === value
|
||||||
@connection.quote value
|
@connection.quote value
|
||||||
|
38
activerecord/test/cases/arel/nodes/fragments_test.rb
Normal file
38
activerecord/test/cases/arel/nodes/fragments_test.rb
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative "../helper"
|
||||||
|
require "yaml"
|
||||||
|
|
||||||
|
module Arel
|
||||||
|
module Nodes
|
||||||
|
class FragmentsTest < Arel::Spec
|
||||||
|
describe "equality" do
|
||||||
|
it "is equal with equal values" do
|
||||||
|
array = [Fragments.new(["foo", "bar"]), Fragments.new(["foo", "bar"])]
|
||||||
|
assert_equal 1, array.uniq.size
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is not equal with different values" do
|
||||||
|
array = [Fragments.new(["foo"]), Fragments.new(["bar"])]
|
||||||
|
assert_equal 2, array.uniq.size
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can be joined with other nodes" do
|
||||||
|
fragments = Fragments.new(["foo", "bar"])
|
||||||
|
sql = Arel.sql("SELECT")
|
||||||
|
joined_fragments = fragments + sql
|
||||||
|
|
||||||
|
assert_equal ["foo", "bar"], fragments.values
|
||||||
|
assert_equal ["foo", "bar", sql], joined_fragments.values
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails if joined with something that is not an Arel node" do
|
||||||
|
fragments = Fragments.new
|
||||||
|
assert_raises ArgumentError do
|
||||||
|
fragments + "Not a node"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -70,6 +70,23 @@ def compile(node)
|
|||||||
assert_equal("foo", YAML.load(yaml_literal))
|
assert_equal("foo", YAML.load(yaml_literal))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "addition" do
|
||||||
|
it "generates a Fragments node" do
|
||||||
|
sql1 = Arel.sql "SELECT *"
|
||||||
|
sql2 = Arel.sql "FROM users"
|
||||||
|
fragments = sql1 + sql2
|
||||||
|
_(fragments).must_be_kind_of Arel::Nodes::Fragments
|
||||||
|
assert_equal([sql1, sql2], fragments.values)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails if joined with something that is not an Arel node" do
|
||||||
|
sql = Arel.sql "SELECT *"
|
||||||
|
assert_raises ArgumentError do
|
||||||
|
sql + "Not a node"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -768,6 +768,20 @@ def dispatch
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "Nodes::Fragments" do
|
||||||
|
it "joins subexpressions" do
|
||||||
|
sql = Arel.sql("SELECT foo, bar") + Arel.sql(" FROM customers")
|
||||||
|
_(compile(sql)).must_be_like "SELECT foo, bar FROM customers"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can be built by adding SQL fragments one at a time" do
|
||||||
|
sql = Arel.sql("SELECT foo, bar")
|
||||||
|
sql += Arel.sql("FROM customers")
|
||||||
|
sql += Arel.sql("GROUP BY foo")
|
||||||
|
_(compile(sql)).must_be_like "SELECT foo, bar FROM customers GROUP BY foo"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user