-
Notifications
You must be signed in to change notification settings - Fork 21.4k
/
node.rb
161 lines (153 loc) · 6.36 KB
/
node.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# frozen_string_literal: true
module Arel # :nodoc: all
module Nodes
# = Using +Arel::Nodes::Node+
#
# Active Record uses Arel to compose SQL statements. Instead of building SQL strings directly, it's building an
# abstract syntax tree (AST) of the statement using various types of Arel::Nodes::Node. Each node represents a
# fragment of a SQL statement.
#
# The intermediate representation allows Arel to compile the statement into the database's specific SQL dialect
# only before sending it without having to care about the nuances of each database when building the statement.
# It also allows easier composition of statements without having to resort to (brittle and unsafe) string manipulation.
#
# == Building constraints
#
# One of the most common use cases of Arel is generating constraints for +SELECT+ statements. To help with that,
# most nodes include a couple of useful factory methods to create subtree structures for common constraints. For
# a full list of those, please refer to Arel::Predications.
#
# The following example creates an equality constraint where the value of the name column on the users table
# matches the value DHH.
#
# users = Arel::Table.new(:users)
# constraint = users[:name].eq("DHH")
#
# # => Arel::Nodes::Equality.new(
# # Arel::Attributes::Attribute.new(users, "name"),
# # Arel::Nodes::Casted.new(
# # "DHH",
# # Arel::Attributes::Attribute.new(users, "name")
# # )
# # )
#
# The resulting SQL fragment will look like this:
#
# "users"."name" = 'DHH'
#
# The constraint fragments can be used with regular ActiveRecord::Relation objects instead of a Hash. The
# following two examples show two ways of creating the same query.
#
# User.where(name: 'DHH')
#
# # SELECT "users".* FROM "users" WHERE "users"."name" = 'DHH'
#
# users = User.arel_table
#
# User.where(users[:name].eq('DHH'))
#
# # SELECT "users".* FROM "users" WHERE "users"."name" = 'DHH'
#
# == Functions
#
# Arel comes with built-in support for SQL functions like +COUNT+, +SUM+, +MIN+, +MAX+, and +AVG+. The
# Arel::Expressions module includes factory methods for the default functions.
#
# employees = Employee.arel_table
#
# Employee.select(employees[:department_id], employees[:salary].average).group(employees[:department_id])
#
# # SELECT "employees"."department_id", AVG("employees"."salary")
# # FROM "employees" GROUP BY "employees"."department_id"
#
# It’s also possible to use custom functions by using the Arel::Nodes::NamedFunction node type. It accepts a
# function name and an array of parameters.
#
# Arel::Nodes::NamedFunction.new('date_trunc', [Arel::Nodes.build_quoted('day'), User.arel_table[:created_at]])
#
# # date_trunc('day', "users"."created_at")
#
# == Quoting & bind params
#
# Values that you pass to Arel nodes need to be quoted or wrapped in bind params. This ensures they are properly
# converted into the correct format without introducing a possible SQL injection vulnerability. Most factory
# methods (like +eq+, +gt+, +lteq+, …) quote passed values automatically. When not using a factory method, it’s
# possible to convert a value and wrap it in an Arel::Nodes::Quoted node (if necessary) by calling +Arel::Nodes.
# build_quoted+.
#
# Arel::Nodes.build_quoted("foo") # 'foo'
# Arel::Nodes.build_quoted(12.3) # 12.3
#
# Instead of quoting values and embedding them directly in the SQL statement, it’s also possible to create bind
# params. This keeps the actual values outside of the statement and allows using the prepared statement feature
# of some databases.
#
# attribute = ActiveRecord::Relation::QueryAttribute.new(:name, "DHH", ActiveRecord::Type::String.new)
# Arel::Nodes::BindParam.new(attribute)
#
# When ActiveRecord runs the query, bind params are replaced by placeholders (like +$1+) and the values are passed
# separately.
#
# == SQL Literals
#
# For cases where there is no way to represent a particular SQL fragment using Arel nodes, you can use an SQL
# literal. SQL literals are strings that Arel will treat “as is”.
#
# Arel.sql('LOWER("users"."name")').eq('dhh')
#
# # LOWER("users"."name") = 'dhh'
#
# Please keep in mind that passing data as raw SQL literals might introduce a possible SQL injection. However,
# `Arel.sql` supports binding parameters which will ensure proper quoting. This can be useful when you need to
# control the exact SQL you run, but you still have potentially user-supplied values.
#
# Arel.sql('LOWER("users"."name") = ?', 'dhh')
#
# # LOWER("users"."name") = 'dhh'
#
# You can also combine SQL literals.
#
# sql = Arel.sql('SELECT * FROM "users" WHERE ')
# sql += Arel.sql('LOWER("users"."name") = :name', name: 'dhh')
# sql += Arel.sql('AND "users"."age" > :age', age: 35)
#
# # SELECT * FROM "users" WHERE LOWER("users"."name") = 'dhh' AND "users"."age" > '35'
class Node
include Arel::FactoryMethods
###
# Factory method to create a Nodes::Not node that has the recipient of
# the caller as a child.
def not
Nodes::Not.new self
end
###
# Factory method to create a Nodes::Grouping node that has an Nodes::Or
# node as a child.
def or(right)
Nodes::Grouping.new Nodes::Or.new([self, right])
end
###
# Factory method to create an Nodes::And node.
def and(right)
Nodes::And.new [self, right]
end
def invert
Arel::Nodes::Not.new(self)
end
# FIXME: this method should go away. I don't like people calling
# to_sql on non-head nodes. This forces us to walk the AST until we
# can find a node that has a "relation" member.
#
# Maybe we should just use `Table.engine`? :'(
def to_sql(engine = Table.engine)
collector = Arel::Collectors::SQLString.new
engine.with_connection do |connection|
connection.visitor.accept(self, collector).value
end
end
def fetch_attribute
end
def equality?; false; end
end
end
end