sqlpostgres 1.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +8 -0
- data/Gemfile.lock +22 -0
- data/LICENSE.md +23 -0
- data/README.rdoc +59 -0
- data/Rakefile +32 -0
- data/VERSION +1 -0
- data/doc/BUGS +2 -0
- data/doc/examples/README +6 -0
- data/doc/examples/connection.rb +16 -0
- data/doc/examples/connection_auto.rb +22 -0
- data/doc/examples/connection_ctor.rb +18 -0
- data/doc/examples/connection_default.rb +15 -0
- data/doc/examples/connection_exec.rb +18 -0
- data/doc/examples/connection_manual.rb +12 -0
- data/doc/examples/connection_wrapped_new.rb +13 -0
- data/doc/examples/connection_wrapped_open.rb +13 -0
- data/doc/examples/cursor.rb +38 -0
- data/doc/examples/include_module.rb +9 -0
- data/doc/examples/include_module2.rb +12 -0
- data/doc/examples/insert.rb +30 -0
- data/doc/examples/insert2.rb +36 -0
- data/doc/examples/insert_bytea.rb +16 -0
- data/doc/examples/insert_bytea_array.rb +17 -0
- data/doc/examples/insert_default_values.rb +16 -0
- data/doc/examples/insert_insert.rb +16 -0
- data/doc/examples/insert_insert_default.rb +16 -0
- data/doc/examples/insert_insert_select.rb +20 -0
- data/doc/examples/insert_select.rb +20 -0
- data/doc/examples/interval.rb +17 -0
- data/doc/examples/savepoint.rb +38 -0
- data/doc/examples/select.rb +33 -0
- data/doc/examples/select2.rb +36 -0
- data/doc/examples/select_cross_join.rb +18 -0
- data/doc/examples/select_distinct.rb +18 -0
- data/doc/examples/select_distinct_on +19 -0
- data/doc/examples/select_for_update.rb +18 -0
- data/doc/examples/select_from.rb +17 -0
- data/doc/examples/select_from_subselect.rb +20 -0
- data/doc/examples/select_group_by.rb +19 -0
- data/doc/examples/select_having.rb +20 -0
- data/doc/examples/select_join_on.rb +18 -0
- data/doc/examples/select_join_using.rb +18 -0
- data/doc/examples/select_limit.rb +19 -0
- data/doc/examples/select_natural_join.rb +18 -0
- data/doc/examples/select_offset.rb +19 -0
- data/doc/examples/select_order_by.rb +20 -0
- data/doc/examples/select_select.rb +30 -0
- data/doc/examples/select_select_alias.rb +30 -0
- data/doc/examples/select_select_expression.rb +31 -0
- data/doc/examples/select_select_literal.rb +24 -0
- data/doc/examples/select_union.rb +21 -0
- data/doc/examples/select_where_array.rb +18 -0
- data/doc/examples/select_where_in.rb +18 -0
- data/doc/examples/select_where_string.rb +18 -0
- data/doc/examples/simple.rb +34 -0
- data/doc/examples/transaction.rb +30 -0
- data/doc/examples/transaction_abort.rb +30 -0
- data/doc/examples/transaction_commit.rb +34 -0
- data/doc/examples/translate_substitute_values.rb +17 -0
- data/doc/examples/update.rb +32 -0
- data/doc/examples/update2.rb +44 -0
- data/doc/examples/update_only.rb +17 -0
- data/doc/examples/update_set.rb +17 -0
- data/doc/examples/update_set_array.rb +16 -0
- data/doc/examples/update_set_bytea.rb +16 -0
- data/doc/examples/update_set_expression.rb +16 -0
- data/doc/examples/update_set_subselect.rb +20 -0
- data/doc/examples/update_where.rb +17 -0
- data/doc/examples/use_prefix.rb +8 -0
- data/doc/examples/use_prefix2.rb +11 -0
- data/doc/index.html +31 -0
- data/doc/insertexamples.rb +9 -0
- data/doc/makemanual +4 -0
- data/doc/makerdoc +5 -0
- data/doc/manual.dbk +622 -0
- data/lib/sqlpostgres/Connection.rb +198 -0
- data/lib/sqlpostgres/Cursor.rb +157 -0
- data/lib/sqlpostgres/Delete.rb +67 -0
- data/lib/sqlpostgres/Exceptions.rb +15 -0
- data/lib/sqlpostgres/Insert.rb +279 -0
- data/lib/sqlpostgres/NullConnection.rb +22 -0
- data/lib/sqlpostgres/PgBit.rb +73 -0
- data/lib/sqlpostgres/PgBox.rb +37 -0
- data/lib/sqlpostgres/PgCidr.rb +21 -0
- data/lib/sqlpostgres/PgCircle.rb +75 -0
- data/lib/sqlpostgres/PgInet.rb +21 -0
- data/lib/sqlpostgres/PgInterval.rb +208 -0
- data/lib/sqlpostgres/PgLineSegment.rb +37 -0
- data/lib/sqlpostgres/PgMacAddr.rb +21 -0
- data/lib/sqlpostgres/PgPath.rb +64 -0
- data/lib/sqlpostgres/PgPoint.rb +65 -0
- data/lib/sqlpostgres/PgPolygon.rb +56 -0
- data/lib/sqlpostgres/PgTime.rb +77 -0
- data/lib/sqlpostgres/PgTimeWithTimeZone.rb +98 -0
- data/lib/sqlpostgres/PgTimestamp.rb +93 -0
- data/lib/sqlpostgres/PgTwoPoints.rb +54 -0
- data/lib/sqlpostgres/PgType.rb +34 -0
- data/lib/sqlpostgres/PgWrapper.rb +41 -0
- data/lib/sqlpostgres/Savepoint.rb +98 -0
- data/lib/sqlpostgres/Select.rb +855 -0
- data/lib/sqlpostgres/Transaction.rb +120 -0
- data/lib/sqlpostgres/Translate.rb +436 -0
- data/lib/sqlpostgres/Update.rb +188 -0
- data/lib/sqlpostgres.rb +67 -0
- data/test/Assert.rb +72 -0
- data/test/Connection.test.rb +246 -0
- data/test/Cursor.test.rb +190 -0
- data/test/Delete.test.rb +68 -0
- data/test/Insert.test.rb +123 -0
- data/test/MockPGconn.rb +62 -0
- data/test/NullConnection.test.rb +32 -0
- data/test/PgBit.test.rb +98 -0
- data/test/PgBox.test.rb +108 -0
- data/test/PgCidr.test.rb +61 -0
- data/test/PgCircle.test.rb +107 -0
- data/test/PgInet.test.rb +61 -0
- data/test/PgInterval.test.rb +180 -0
- data/test/PgLineSegment.test.rb +108 -0
- data/test/PgMacAddr.test.rb +61 -0
- data/test/PgPath.test.rb +106 -0
- data/test/PgPoint.test.rb +100 -0
- data/test/PgPolygon.test.rb +95 -0
- data/test/PgTime.test.rb +120 -0
- data/test/PgTimeWithTimeZone.test.rb +117 -0
- data/test/PgTimestamp.test.rb +134 -0
- data/test/RandomThings.rb +25 -0
- data/test/Savepoint.test.rb +286 -0
- data/test/Select.test.rb +930 -0
- data/test/Test.rb +62 -0
- data/test/TestConfig.rb +21 -0
- data/test/TestSetup.rb +13 -0
- data/test/TestUtil.rb +92 -0
- data/test/Transaction.test.rb +275 -0
- data/test/Translate.test.rb +354 -0
- data/test/Update.test.rb +227 -0
- data/test/roundtrip.test.rb +565 -0
- data/test/test +34 -0
- data/tools/exampleinserter/ExampleInserter.rb +177 -0
- data/tools/rdoc/ChangeLog +796 -0
- data/tools/rdoc/EXAMPLE.rb +48 -0
- data/tools/rdoc/MANIFEST +58 -0
- data/tools/rdoc/Makefile +27 -0
- data/tools/rdoc/NEW_FEATURES +226 -0
- data/tools/rdoc/README +390 -0
- data/tools/rdoc/ToDo +6 -0
- data/tools/rdoc/contrib/Index +6 -0
- data/tools/rdoc/contrib/xslfo/ChangeLog +181 -0
- data/tools/rdoc/contrib/xslfo/README +106 -0
- data/tools/rdoc/contrib/xslfo/TODO +10 -0
- data/tools/rdoc/contrib/xslfo/convert.xsl +151 -0
- data/tools/rdoc/contrib/xslfo/demo/README +21 -0
- data/tools/rdoc/contrib/xslfo/demo/rdocfo +99 -0
- data/tools/rdoc/contrib/xslfo/fcm.xsl +54 -0
- data/tools/rdoc/contrib/xslfo/files.xsl +62 -0
- data/tools/rdoc/contrib/xslfo/labeled-lists.xsl +66 -0
- data/tools/rdoc/contrib/xslfo/lists.xsl +44 -0
- data/tools/rdoc/contrib/xslfo/modules.xsl +152 -0
- data/tools/rdoc/contrib/xslfo/rdoc.xsl +75 -0
- data/tools/rdoc/contrib/xslfo/source.xsl +66 -0
- data/tools/rdoc/contrib/xslfo/styles.xsl +69 -0
- data/tools/rdoc/contrib/xslfo/tables.xsl +67 -0
- data/tools/rdoc/contrib/xslfo/utils.xsl +21 -0
- data/tools/rdoc/debian/changelog +33 -0
- data/tools/rdoc/debian/compat +1 -0
- data/tools/rdoc/debian/control +20 -0
- data/tools/rdoc/debian/copyright +10 -0
- data/tools/rdoc/debian/dirs +2 -0
- data/tools/rdoc/debian/docs +2 -0
- data/tools/rdoc/debian/rdoc.1 +252 -0
- data/tools/rdoc/debian/rdoc.manpages +1 -0
- data/tools/rdoc/debian/rdoc.pod +149 -0
- data/tools/rdoc/debian/rules +9 -0
- data/tools/rdoc/dot/dot.rb +255 -0
- data/tools/rdoc/etc/rdoc.dtd +203 -0
- data/tools/rdoc/install.rb +137 -0
- data/tools/rdoc/markup/install.rb +43 -0
- data/tools/rdoc/markup/sample/sample.rb +42 -0
- data/tools/rdoc/markup/simple_markup/fragments.rb +323 -0
- data/tools/rdoc/markup/simple_markup/inline.rb +348 -0
- data/tools/rdoc/markup/simple_markup/lines.rb +147 -0
- data/tools/rdoc/markup/simple_markup/preprocess.rb +68 -0
- data/tools/rdoc/markup/simple_markup/to_html.rb +281 -0
- data/tools/rdoc/markup/simple_markup.rb +474 -0
- data/tools/rdoc/markup/test/AllTests.rb +2 -0
- data/tools/rdoc/markup/test/TestInline.rb +151 -0
- data/tools/rdoc/markup/test/TestParse.rb +411 -0
- data/tools/rdoc/rdoc/code_objects.rb +536 -0
- data/tools/rdoc/rdoc/diagram.rb +331 -0
- data/tools/rdoc/rdoc/generators/chm_generator.rb +112 -0
- data/tools/rdoc/rdoc/generators/html_generator.rb +1268 -0
- data/tools/rdoc/rdoc/generators/template/chm/chm.rb +86 -0
- data/tools/rdoc/rdoc/generators/template/html/html.rb +705 -0
- data/tools/rdoc/rdoc/generators/template/html/kilmer.rb +377 -0
- data/tools/rdoc/rdoc/generators/template/xml/rdf.rb +110 -0
- data/tools/rdoc/rdoc/generators/template/xml/xml.rb +110 -0
- data/tools/rdoc/rdoc/generators/xml_generator.rb +130 -0
- data/tools/rdoc/rdoc/options.rb +451 -0
- data/tools/rdoc/rdoc/parsers/parse_c.rb +287 -0
- data/tools/rdoc/rdoc/parsers/parse_f95.rb +118 -0
- data/tools/rdoc/rdoc/parsers/parse_rb.rb +2311 -0
- data/tools/rdoc/rdoc/parsers/parse_simple.rb +37 -0
- data/tools/rdoc/rdoc/parsers/parserfactory.rb +75 -0
- data/tools/rdoc/rdoc/rdoc.rb +219 -0
- data/tools/rdoc/rdoc/template.rb +234 -0
- data/tools/rdoc/rdoc/tokenstream.rb +25 -0
- data/tools/rdoc/rdoc.rb +9 -0
- metadata +291 -0
@@ -0,0 +1,855 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module SqlPostgres
|
4
|
+
|
5
|
+
# This class creates and executes an SQL select statement.
|
6
|
+
#
|
7
|
+
# Example (assuming the values 1, 2 and null are in the database):
|
8
|
+
#
|
9
|
+
#** Example: select
|
10
|
+
# select = Select.new(connection)
|
11
|
+
# select.select('i')
|
12
|
+
# select.from('foo')
|
13
|
+
# select.order_by('i')
|
14
|
+
# p select.statement # "select i from foo order by i"
|
15
|
+
# p select.exec # [{"i"=>1}, {"i"=>2}, {"i"=>nil}]
|
16
|
+
#**
|
17
|
+
|
18
|
+
class Select
|
19
|
+
|
20
|
+
# Constructor. If no connection is given, uses the default.
|
21
|
+
|
22
|
+
def initialize(connection = Connection.default)
|
23
|
+
@connection = connection
|
24
|
+
@tables = []
|
25
|
+
@columns = []
|
26
|
+
@joins = []
|
27
|
+
@order_by = []
|
28
|
+
@where = []
|
29
|
+
@group_by = []
|
30
|
+
@having = []
|
31
|
+
@limit = nil
|
32
|
+
@offset = nil
|
33
|
+
@distinct = false
|
34
|
+
@distinct_on = []
|
35
|
+
@set_ops = []
|
36
|
+
@for_update = false
|
37
|
+
end
|
38
|
+
|
39
|
+
# Add an expression (usually just a simple column name)
|
40
|
+
# to the select statement.
|
41
|
+
#
|
42
|
+
# [expression]
|
43
|
+
# The expression to put in the select statement. Should be one of:
|
44
|
+
# [An instance of Select] The Select's SQL statement is put in
|
45
|
+
# parentheses and added to this statement.
|
46
|
+
# [String or Array] Converted by #substitute_values
|
47
|
+
# [as]
|
48
|
+
# The alias name to put in the statement and to use as the hash key
|
49
|
+
# in the result. If nil, then no alias name appears in the statement
|
50
|
+
# and the expression is used as the hash key.
|
51
|
+
#
|
52
|
+
# Example:
|
53
|
+
#** Example: select_select
|
54
|
+
# select = Select.new(connection)
|
55
|
+
# select.select('i')
|
56
|
+
# select.from('foo')
|
57
|
+
# p select.statement # "select i from foo"
|
58
|
+
# p select.exec # [{"i"=>1}]
|
59
|
+
#**
|
60
|
+
#
|
61
|
+
# Example (alias)
|
62
|
+
#** Example: select_select_alias
|
63
|
+
# select = Select.new(connection)
|
64
|
+
# select.select('i', 'number')
|
65
|
+
# select.from('foo')
|
66
|
+
# p select.statement # "select i as number from foo"
|
67
|
+
# p select.exec # [{"number"=>1}]
|
68
|
+
#**
|
69
|
+
#
|
70
|
+
# Example (expression)
|
71
|
+
#** Example: select_select_expression
|
72
|
+
# pi = 3.14
|
73
|
+
# select = Select.new(connection)
|
74
|
+
# select.select(['d * %s', pi], 'c')
|
75
|
+
# select.from('circles')
|
76
|
+
# p select.statement # "select d * 3.14 as c from circles"
|
77
|
+
# p select.exec # [{"c"=>6.28}]
|
78
|
+
#**
|
79
|
+
|
80
|
+
def select(expression, as = nil, &converter)
|
81
|
+
converter ||= AutoConverter
|
82
|
+
expression = if expression.is_a?(Select)
|
83
|
+
"(#{expression.statement})"
|
84
|
+
else
|
85
|
+
Translate.substitute_values(expression)
|
86
|
+
end
|
87
|
+
@columns << Column.new(expression, as, converter)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Select a literal, automatically selecting its result type.
|
91
|
+
#
|
92
|
+
# [value]
|
93
|
+
# The value to put in the statement. This can be any of these types:
|
94
|
+
# * nil
|
95
|
+
# * Integer
|
96
|
+
# * Float
|
97
|
+
# * String
|
98
|
+
# * true or false
|
99
|
+
# * #PgTime
|
100
|
+
# * #PgInterval
|
101
|
+
# * #PgTimeWithTimeZone
|
102
|
+
# * #PgTimestamp
|
103
|
+
# * #PgPoint
|
104
|
+
# * #PgLineSegment
|
105
|
+
# * #PgBox
|
106
|
+
# * #PgPath
|
107
|
+
# * #PgPolygon
|
108
|
+
# * #PgCircle
|
109
|
+
# * #PgBit
|
110
|
+
# * #PgInet
|
111
|
+
# * #PgCidr
|
112
|
+
# * #PgMacAddr
|
113
|
+
#
|
114
|
+
# [as]
|
115
|
+
# The alias name to put in the statement and to use as the hash
|
116
|
+
# key in the result. If nil, then no alias name appears in the
|
117
|
+
# statement and the value itself is used as the hash key.
|
118
|
+
#
|
119
|
+
# Example:
|
120
|
+
#** Example: select_select_literal
|
121
|
+
# select = Select.new(connection)
|
122
|
+
# select.select_literal(2, 'n')
|
123
|
+
# select.select_literal('foo', 't')
|
124
|
+
# p select.statement # "select 2 as n, E'foo' as t"
|
125
|
+
# p select.exec # [{"n"=>2, "t"=>"foo"}]
|
126
|
+
#**
|
127
|
+
|
128
|
+
def select_literal(value, as = nil)
|
129
|
+
select(["%s", value], as)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Add "distinct" to this statement.
|
133
|
+
#
|
134
|
+
# Example:
|
135
|
+
#** Example: select_distinct
|
136
|
+
# select = Select.new
|
137
|
+
# select.distinct
|
138
|
+
# select.select('i')
|
139
|
+
# select.from('foo')
|
140
|
+
# p select.statement # "select distinct i from foo"
|
141
|
+
#**
|
142
|
+
|
143
|
+
def distinct
|
144
|
+
@distinct = true
|
145
|
+
end
|
146
|
+
|
147
|
+
# Add "distinct on" to this statement.
|
148
|
+
# "distinct on" is a postgres extension.
|
149
|
+
#
|
150
|
+
# Example:
|
151
|
+
#** Example: select_distinct_on
|
152
|
+
# select = Select.new
|
153
|
+
# select.distinct_on('i')
|
154
|
+
# select.select('integer', 'i')
|
155
|
+
# select.select('integer', 'j')
|
156
|
+
# select.from('foo')
|
157
|
+
# p select.statement # "select distinct on (i) i, j from
|
158
|
+
# # foo"
|
159
|
+
#**
|
160
|
+
|
161
|
+
def distinct_on(expression)
|
162
|
+
@distinct_on << expression
|
163
|
+
end
|
164
|
+
|
165
|
+
# Add the "from" clause to the statement.
|
166
|
+
#
|
167
|
+
# [table]
|
168
|
+
# What's being selected from, which is either
|
169
|
+
# * A table name, or
|
170
|
+
# * a Select statement
|
171
|
+
# [as]
|
172
|
+
# The alias name, or nil if there isn't one
|
173
|
+
#
|
174
|
+
# Table example:
|
175
|
+
#** Example: select_from
|
176
|
+
# select = Select.new
|
177
|
+
# select.select('i')
|
178
|
+
# select.from('foo')
|
179
|
+
# p select.statement # "select i from foo"
|
180
|
+
#**
|
181
|
+
#
|
182
|
+
# Subselect example:
|
183
|
+
#** Example: select_from_subselect
|
184
|
+
# subselect = Select.new
|
185
|
+
# subselect.select('i')
|
186
|
+
# subselect.from('foo')
|
187
|
+
# select = Select.new
|
188
|
+
# select.select('i')
|
189
|
+
# select.from(subselect, 'bar')
|
190
|
+
# p select.statement # "select i from (select i from foo) as bar"
|
191
|
+
#**
|
192
|
+
|
193
|
+
def from(table, as = nil)
|
194
|
+
table = "(#{table.statement})" if table.is_a?(Select)
|
195
|
+
@tables << [table, as].compact.join(' as ')
|
196
|
+
end
|
197
|
+
|
198
|
+
# Add the "union" set operation to this statement.
|
199
|
+
# You may call this multiple times.
|
200
|
+
#
|
201
|
+
# The right-hand-side of the union is put in parentheses.
|
202
|
+
# This makes it possible to force the order when doing multiple
|
203
|
+
# set operations.
|
204
|
+
#
|
205
|
+
# Example
|
206
|
+
#** Example: select_union
|
207
|
+
# select2 = Select.new
|
208
|
+
# select2.select('i')
|
209
|
+
# select2.from('bar')
|
210
|
+
# select1 = Select.new
|
211
|
+
# select1.select('i')
|
212
|
+
# select1.from('foo')
|
213
|
+
# select1.union(select2)
|
214
|
+
# p select1.statement # "select i from foo union (select i from
|
215
|
+
# # bar)"
|
216
|
+
#**
|
217
|
+
|
218
|
+
def union(select)
|
219
|
+
add_set_op('union', select)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Add the "union all" set operation to this statement.
|
223
|
+
# See #union.
|
224
|
+
|
225
|
+
def union_all(select)
|
226
|
+
add_set_op('union all', select)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Add the "intersect" set operation to this statement.
|
230
|
+
# See #union.
|
231
|
+
|
232
|
+
def intersect(select)
|
233
|
+
add_set_op('intersect', select)
|
234
|
+
end
|
235
|
+
|
236
|
+
# Add the "intersect all" set operation to this statement.
|
237
|
+
# See #union.
|
238
|
+
|
239
|
+
def intersect_all(select)
|
240
|
+
add_set_op('intersect all', select)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Add the "except" set operation to this statement.
|
244
|
+
# See #union.
|
245
|
+
|
246
|
+
def except(select)
|
247
|
+
add_set_op('except', select)
|
248
|
+
end
|
249
|
+
|
250
|
+
# Add the "except all" set operation to this statement.
|
251
|
+
# See #union.
|
252
|
+
|
253
|
+
def except_all(select)
|
254
|
+
add_set_op('except all', select)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Add a natural join to this statement.
|
258
|
+
#
|
259
|
+
# Example:
|
260
|
+
#** Example: select_natural_join
|
261
|
+
# select = Select.new
|
262
|
+
# select.select('i')
|
263
|
+
# select.from('foo')
|
264
|
+
# select.natural_join('bar')
|
265
|
+
# p select.statement # "select i from foo natural join bar"
|
266
|
+
#**
|
267
|
+
|
268
|
+
def natural_join(table)
|
269
|
+
@joins << "natural join #{table}"
|
270
|
+
end
|
271
|
+
|
272
|
+
# Add a "join using" to this statement.
|
273
|
+
#
|
274
|
+
# [joinType]
|
275
|
+
# One of:
|
276
|
+
# * 'inner'
|
277
|
+
# * 'left outer'
|
278
|
+
# * 'right outer'
|
279
|
+
# * 'full outer'
|
280
|
+
# [table]
|
281
|
+
# The table being joined
|
282
|
+
# [*columns]
|
283
|
+
# One or more column names.
|
284
|
+
#
|
285
|
+
# Example:
|
286
|
+
#** Example: select_join_using
|
287
|
+
# select = Select.new
|
288
|
+
# select.select('i')
|
289
|
+
# select.from('foo')
|
290
|
+
# select.join_using('inner', 'bar', 'i', 'j')
|
291
|
+
# p select.statement # "select i from foo inner join bar using (i, j)"
|
292
|
+
#**
|
293
|
+
|
294
|
+
def join_using(joinType, table, *columns)
|
295
|
+
@joins << "#{joinType} join #{table} using (#{columns.join(', ')})"
|
296
|
+
end
|
297
|
+
|
298
|
+
# Add a "join on" to this statement.
|
299
|
+
#
|
300
|
+
# [joinType]
|
301
|
+
# One of:
|
302
|
+
# * 'inner'
|
303
|
+
# * 'left outer'
|
304
|
+
# * 'right outer'
|
305
|
+
# * 'full outer'
|
306
|
+
# [table]
|
307
|
+
# The table being joined
|
308
|
+
# [condition]
|
309
|
+
# A string or array that will be converted by #substitute_values
|
310
|
+
# and inserted into the statement.
|
311
|
+
#
|
312
|
+
# Example:
|
313
|
+
#** Example: select_join_on
|
314
|
+
# select = Select.new
|
315
|
+
# select.select('i')
|
316
|
+
# select.from('foo')
|
317
|
+
# select.join_on('inner', 'bar', 'foo.i = bar.j')
|
318
|
+
# p select.statement # "select i from foo inner join bar on (foo.i =
|
319
|
+
# # bar.j)"
|
320
|
+
#**
|
321
|
+
|
322
|
+
def join_on(joinType, table, condition)
|
323
|
+
@joins << ("#{joinType} join #{table} on "\
|
324
|
+
"(#{Translate.substitute_values(condition)})")
|
325
|
+
end
|
326
|
+
|
327
|
+
# Add a "cross join" to this statement.
|
328
|
+
#
|
329
|
+
# Example:
|
330
|
+
#** Example: select_cross_join
|
331
|
+
# select = Select.new
|
332
|
+
# select.select('i')
|
333
|
+
# select.from('foo')
|
334
|
+
# select.cross_join('bar')
|
335
|
+
# p select.statement # "select i from foo cross join bar"
|
336
|
+
#**
|
337
|
+
|
338
|
+
def cross_join(table)
|
339
|
+
@joins << "cross join #{table}"
|
340
|
+
end
|
341
|
+
|
342
|
+
# Add an "order by" to this statement. You can call this as many
|
343
|
+
# times as you need.
|
344
|
+
#
|
345
|
+
# [expression]
|
346
|
+
# A string or array that will be converted by #substitute_values
|
347
|
+
# and inserted into the statement.
|
348
|
+
# [ordering]
|
349
|
+
# One of:
|
350
|
+
# 'asc':: ascending
|
351
|
+
# 'desc':: descending
|
352
|
+
# nil:: default ordering, which is ascending
|
353
|
+
#
|
354
|
+
# Example:
|
355
|
+
#** Example: select_order_by
|
356
|
+
# select = Select.new
|
357
|
+
# select.select('i')
|
358
|
+
# select.select('j')
|
359
|
+
# select.from('foo')
|
360
|
+
# select.order_by('i')
|
361
|
+
# select.order_by('j', 'desc')
|
362
|
+
# p select.statement # "select i, j from foo order by i, j desc"
|
363
|
+
#**
|
364
|
+
|
365
|
+
def order_by(expression, ordering = nil)
|
366
|
+
@order_by <<
|
367
|
+
[Translate.substitute_values(expression), ordering].compact.join(' ')
|
368
|
+
end
|
369
|
+
|
370
|
+
# Add a "where" condition to this statement.
|
371
|
+
#
|
372
|
+
# [expression]
|
373
|
+
# The condition. Should be one of:
|
374
|
+
# [A string] The expression
|
375
|
+
# [An array] An expression converted using #substitute_values
|
376
|
+
#
|
377
|
+
# Example (string)
|
378
|
+
#** Example: select_where_string
|
379
|
+
# select = Select.new
|
380
|
+
# select.select('i')
|
381
|
+
# select.from('foo')
|
382
|
+
# select.where('i > 0')
|
383
|
+
# p select.statement # "select i from foo where i > 0"
|
384
|
+
#**
|
385
|
+
#
|
386
|
+
# Example (array)
|
387
|
+
#** Example: select_where_array
|
388
|
+
# select = Select.new
|
389
|
+
# select.select('age')
|
390
|
+
# select.from('person')
|
391
|
+
# select.where(['name = %s', 'Smith'])
|
392
|
+
# p select.statement # "select age from person where name =
|
393
|
+
# # E'Smith'"
|
394
|
+
#**
|
395
|
+
#
|
396
|
+
# Example (in)
|
397
|
+
#** Example: select_where_in
|
398
|
+
# select = Select.new
|
399
|
+
# select.select('s')
|
400
|
+
# select.from('foo')
|
401
|
+
# select.where(['s in %s', [:in, 'foo', 'bar']])
|
402
|
+
# p select.statement # "select s from foo where s in (E'foo',
|
403
|
+
# # E'bar')"
|
404
|
+
#**
|
405
|
+
|
406
|
+
def where(expression)
|
407
|
+
@where << Translate.substitute_values(expression)
|
408
|
+
end
|
409
|
+
|
410
|
+
# Add a "group by" to this statement.
|
411
|
+
#
|
412
|
+
# [expression]
|
413
|
+
# A string or array that will be converted by #substitute_values
|
414
|
+
# and inserted into the statement.
|
415
|
+
#
|
416
|
+
# Example
|
417
|
+
#** Example: select_group_by
|
418
|
+
# select = Select.new
|
419
|
+
# select.select('i')
|
420
|
+
# select.select('count(*)', 'count')
|
421
|
+
# select.from('foo')
|
422
|
+
# select.group_by('i')
|
423
|
+
# p select.statement # "select i, count(*) as count from foo group
|
424
|
+
# # by i"
|
425
|
+
#**
|
426
|
+
|
427
|
+
def group_by(expression)
|
428
|
+
@group_by << Translate.substitute_values(expression)
|
429
|
+
end
|
430
|
+
|
431
|
+
# Add a "having" clause to this statement.
|
432
|
+
#
|
433
|
+
# [expression]
|
434
|
+
# A string or array that will be converted by #substitute_values
|
435
|
+
# and inserted into the statement.
|
436
|
+
#
|
437
|
+
# Example
|
438
|
+
#** Example: select_having
|
439
|
+
# select = Select.new
|
440
|
+
# select.select('i')
|
441
|
+
# select.select('count(*)', 'count')
|
442
|
+
# select.from('foo')
|
443
|
+
# select.group_by('i')
|
444
|
+
# select.having('i < 10')
|
445
|
+
# p select.statement # "select i, count(*) as count from foo
|
446
|
+
# # group by i having i < 10"
|
447
|
+
#**
|
448
|
+
|
449
|
+
def having(expression)
|
450
|
+
@having << Translate.substitute_values(expression)
|
451
|
+
end
|
452
|
+
|
453
|
+
# Add a "limit" clause to the statement.
|
454
|
+
#
|
455
|
+
# Example:
|
456
|
+
#** Example: select_limit
|
457
|
+
# select = Select.new
|
458
|
+
# select.select('i')
|
459
|
+
# select.from('foo')
|
460
|
+
# select.order_by('i')
|
461
|
+
# select.limit(1)
|
462
|
+
# p select.statement # "select i from foo order by i limit 1"
|
463
|
+
#**
|
464
|
+
|
465
|
+
def limit(value)
|
466
|
+
@limit = value
|
467
|
+
end
|
468
|
+
|
469
|
+
# Add an "offset" clause to the statement.
|
470
|
+
#
|
471
|
+
# Example:
|
472
|
+
#** Example: select_offset
|
473
|
+
# select = Select.new
|
474
|
+
# select.select('i')
|
475
|
+
# select.from('foo')
|
476
|
+
# select.order_by('i')
|
477
|
+
# select.offset(1)
|
478
|
+
# p select.statement # "select i from foo order by i offset 1"
|
479
|
+
#**
|
480
|
+
|
481
|
+
def offset(value)
|
482
|
+
@offset = value
|
483
|
+
end
|
484
|
+
|
485
|
+
# Add "for update" to the statement.
|
486
|
+
#
|
487
|
+
# Example:
|
488
|
+
#** Example: select_for_update
|
489
|
+
# select = Select.new
|
490
|
+
# select.select('i')
|
491
|
+
# select.from('foo')
|
492
|
+
# select.for_update
|
493
|
+
# p select.statement # "select i from foo for update"
|
494
|
+
#**
|
495
|
+
|
496
|
+
def for_update
|
497
|
+
@for_update = true
|
498
|
+
end
|
499
|
+
|
500
|
+
# Return the SQL statement.
|
501
|
+
|
502
|
+
def statement
|
503
|
+
"select#{distinct_clause} #{expression_list}"\
|
504
|
+
"#{tableExpression}#{join_clause}"\
|
505
|
+
"#{where_clause}#{set_ops_clause}#{group_by_clause}#{having_clause}#{order_by_clause}"\
|
506
|
+
"#{limit_clause}#{offset_clause}#{for_update_clause}"
|
507
|
+
end
|
508
|
+
|
509
|
+
# Execute the statement and return an array of hashes with the result.
|
510
|
+
#
|
511
|
+
# [connection]
|
512
|
+
# If present, the connection to use.
|
513
|
+
# If nil, uses the connection passed to new or, if no connection was
|
514
|
+
# passed to new, uses the default connection.
|
515
|
+
|
516
|
+
def exec(connection = @connection)
|
517
|
+
query_and_translate(connection, statement)
|
518
|
+
end
|
519
|
+
|
520
|
+
# Fetch a row or rows from the cursor. Not intended for consumer
|
521
|
+
# use; it's hanging out here in public for the use of the Cursor
|
522
|
+
# class.
|
523
|
+
#
|
524
|
+
# [cursor_name]
|
525
|
+
# The cursor's name
|
526
|
+
# [direction]
|
527
|
+
# A string specifying which row or rows to fetch (see Cursor.fetch)
|
528
|
+
# [connection]
|
529
|
+
# If present, the connection to use.
|
530
|
+
# If nil, uses the connection passed to new or, if no connection was
|
531
|
+
# passed to new, uses the default connection.
|
532
|
+
|
533
|
+
def fetch_by_cursor(cursor_name, direction, connection = @connection)
|
534
|
+
statement = "fetch #{direction} from #{cursor_name}"
|
535
|
+
query_and_translate(connection, statement)
|
536
|
+
end
|
537
|
+
|
538
|
+
private
|
539
|
+
|
540
|
+
Column = Struct.new(:value, :as, :converter)
|
541
|
+
|
542
|
+
# OIDs for Postgresql data types. These must match
|
543
|
+
# postgresql/server/catalog/pg_type.h.
|
544
|
+
|
545
|
+
module Types
|
546
|
+
BOOLEAN = 16
|
547
|
+
BYTEA = 17
|
548
|
+
QCHAR = 18
|
549
|
+
NAME = 19
|
550
|
+
BIGINT = INT8 = BIGSERIAL = SERIAL8 = 20
|
551
|
+
SMALLINT = 21
|
552
|
+
INTEGER = INT = INT4 = SERIAL = 23
|
553
|
+
TEXT = 25
|
554
|
+
OID = 26
|
555
|
+
POINT = 600
|
556
|
+
LSEG = 601
|
557
|
+
PATH = 602
|
558
|
+
BOX = 603
|
559
|
+
POLYGON = 604
|
560
|
+
CIDR = 650
|
561
|
+
ARRAY_CIDR = 651
|
562
|
+
REAL = 700
|
563
|
+
DOUBLE_PRECISION = FLOAT8 = 701
|
564
|
+
UNKNOWN = 705
|
565
|
+
CIRCLE = 718
|
566
|
+
ARRAY_CIRCLE = 719
|
567
|
+
MACADDR = 829
|
568
|
+
INET = 869
|
569
|
+
ARRAY_BOOLEAN = 1000
|
570
|
+
ARRAY_BYTEA = 1001
|
571
|
+
ARRAY_QCHAR = 1002
|
572
|
+
ARRAY_NAME = 1003
|
573
|
+
ARRAY_SMALLINT = 1005
|
574
|
+
ARRAY_INTEGER = 1007
|
575
|
+
ARRAY_TEXT = 1009
|
576
|
+
ARRAY_CHARACTER = 1014
|
577
|
+
ARRAY_VARCHAR = 1015
|
578
|
+
ARRAY_BIGINT = 1016
|
579
|
+
ARRAY_POINT = 1017
|
580
|
+
ARRAY_LSEG = 1018
|
581
|
+
ARRAY_PATH = 1019
|
582
|
+
ARRAY_BOX = 1020
|
583
|
+
ARRAY_REAL = 1021
|
584
|
+
ARRAY_DOUBLE_PRECISION = 1022
|
585
|
+
ARRAY_POLYGON = 1027
|
586
|
+
ARRAY_MACADDR = 1040
|
587
|
+
ARRAY_INET = 1041
|
588
|
+
CHARACTER = CHAR = 1042
|
589
|
+
VARCHAR = CHARACTER_VARYING = 1043
|
590
|
+
DATE = 1082
|
591
|
+
TIME = 1083
|
592
|
+
TIMESTAMP = TIMESTAMP_WITHOUT_TIME_ZONE = 1114
|
593
|
+
ARRAY_TIMESTAMP = 1115
|
594
|
+
ARRAY_DATE = 1182
|
595
|
+
ARRAY_TIME = 1183
|
596
|
+
TIMESTAMP_WITH_TIME_ZONE = 1184
|
597
|
+
ARRAY_TIMESTAMP_WITH_TIME_ZONE = 1185
|
598
|
+
INTERVAL = 1186
|
599
|
+
ARRAY_INTERVAL = 1187
|
600
|
+
ARRAY_NUMERIC = 1231
|
601
|
+
TIME_WITH_TIME_ZONE = 1266
|
602
|
+
ARRAY_TIME_WITH_TIME_ZONE = 1270
|
603
|
+
BIT = 1560
|
604
|
+
ARRAY_BIT = 1561
|
605
|
+
VARBIT = 1562
|
606
|
+
ARRAY_VARBIT = 1563
|
607
|
+
NUMERIC = DECIMAL = 1700
|
608
|
+
end
|
609
|
+
|
610
|
+
# Converters used to translate strings into Ruby types.
|
611
|
+
|
612
|
+
BitConverter = proc { |s| PgBit.from_sql(s) }
|
613
|
+
BooleanConverter = proc { |s| s == 't' }
|
614
|
+
BoxConverter = proc { |s| PgBox.from_sql(s) }
|
615
|
+
ByteaConverter = proc { |s| Translate.unescape_bytea(s) }
|
616
|
+
CidrConverter = proc { |s| PgCidr.from_sql(s) }
|
617
|
+
CircleConverter = proc { |s| PgCircle.from_sql(s) }
|
618
|
+
DateConverter = proc { |s| Translate.sql_to_date(s) }
|
619
|
+
FloatConverter = proc { |s| s.to_f }
|
620
|
+
InetConverter = proc { |s| PgInet.from_sql(s) }
|
621
|
+
IntegerConverter = proc { |s| s.to_i }
|
622
|
+
IntervalConverter = proc { |s| PgInterval.from_sql(s) }
|
623
|
+
LsegConverter = proc { |s| PgLineSegment.from_sql(s) }
|
624
|
+
MacAddrConverter = proc { |s| PgMacAddr.from_sql(s) }
|
625
|
+
PathConverter = proc { |s| PgPath.from_sql(s) }
|
626
|
+
PointConverter = proc { |s| PgPoint.from_sql(s) }
|
627
|
+
PolygonConverter = proc { |s| PgPolygon.from_sql(s) }
|
628
|
+
QCharConverter = proc { |s| Translate.unescape_qchar(s) }
|
629
|
+
StringConverter = proc { |s| s }
|
630
|
+
TimeConverter = proc { |s| PgTime.from_sql(s) }
|
631
|
+
TimeStringConverter = proc { |s| Time.local(*s.split(/:/)) }
|
632
|
+
TimeWithTimeZoneConverter = proc { |s| PgTimeWithTimeZone.from_sql(s) }
|
633
|
+
TimestampConverter = proc { |s| PgTimestamp.from_sql(s) }
|
634
|
+
TimestampTzConverter = proc { |s| Translate.sql_to_datetime(s) }
|
635
|
+
|
636
|
+
# Map each base (non-array) type to a converter.
|
637
|
+
|
638
|
+
CONVERTERS = {
|
639
|
+
Types::BIGINT => IntegerConverter,
|
640
|
+
Types::BIT => BitConverter,
|
641
|
+
Types::BOOLEAN => BooleanConverter,
|
642
|
+
Types::BOX => BoxConverter,
|
643
|
+
Types::BYTEA => ByteaConverter,
|
644
|
+
Types::CHARACTER => StringConverter,
|
645
|
+
Types::CIDR => CidrConverter,
|
646
|
+
Types::CIRCLE => CircleConverter,
|
647
|
+
Types::DATE => DateConverter,
|
648
|
+
Types::DOUBLE_PRECISION => FloatConverter,
|
649
|
+
Types::INET => InetConverter,
|
650
|
+
Types::INTEGER => IntegerConverter,
|
651
|
+
Types::INTERVAL => IntervalConverter,
|
652
|
+
Types::LSEG => LsegConverter,
|
653
|
+
Types::MACADDR => MacAddrConverter,
|
654
|
+
Types::NAME => StringConverter,
|
655
|
+
Types::NUMERIC => FloatConverter,
|
656
|
+
Types::OID => IntegerConverter,
|
657
|
+
Types::PATH => PathConverter,
|
658
|
+
Types::POINT => PointConverter,
|
659
|
+
Types::POLYGON => PolygonConverter,
|
660
|
+
Types::QCHAR => QCharConverter,
|
661
|
+
Types::REAL => FloatConverter,
|
662
|
+
Types::SMALLINT => IntegerConverter,
|
663
|
+
Types::TEXT => StringConverter,
|
664
|
+
Types::TIME => TimeConverter,
|
665
|
+
Types::TIMESTAMP => TimestampConverter,
|
666
|
+
Types::TIMESTAMP_WITH_TIME_ZONE => TimestampTzConverter,
|
667
|
+
Types::TIME_WITH_TIME_ZONE => TimeWithTimeZoneConverter,
|
668
|
+
Types::UNKNOWN => StringConverter,
|
669
|
+
Types::VARBIT => BitConverter,
|
670
|
+
Types::VARCHAR => StringConverter,
|
671
|
+
}
|
672
|
+
|
673
|
+
# Map each array type to its base type.
|
674
|
+
|
675
|
+
ARRAY_ELEMENT_TYPES = {
|
676
|
+
Types::ARRAY_BIGINT => Types::BIGINT,
|
677
|
+
Types::ARRAY_BIT => Types::BIT,
|
678
|
+
Types::ARRAY_BOOLEAN => Types::BOOLEAN,
|
679
|
+
Types::ARRAY_BOX => Types::BOX,
|
680
|
+
Types::ARRAY_BYTEA => Types::BYTEA,
|
681
|
+
Types::ARRAY_CHARACTER => Types::CHARACTER,
|
682
|
+
Types::ARRAY_INET => Types::INET,
|
683
|
+
Types::ARRAY_CIDR => Types::CIDR,
|
684
|
+
Types::ARRAY_CIRCLE => Types::CIRCLE,
|
685
|
+
Types::ARRAY_DATE => Types::DATE,
|
686
|
+
Types::ARRAY_DOUBLE_PRECISION => Types::DOUBLE_PRECISION,
|
687
|
+
Types::ARRAY_INTEGER => Types::INTEGER,
|
688
|
+
Types::ARRAY_INTERVAL => Types::INTERVAL,
|
689
|
+
Types::ARRAY_LSEG => Types::LSEG,
|
690
|
+
Types::ARRAY_MACADDR => Types::MACADDR,
|
691
|
+
Types::ARRAY_NAME => Types::NAME,
|
692
|
+
Types::ARRAY_NUMERIC => Types::NUMERIC,
|
693
|
+
Types::ARRAY_PATH => Types::PATH,
|
694
|
+
Types::ARRAY_POINT => Types::POINT,
|
695
|
+
Types::ARRAY_POLYGON => Types::POLYGON,
|
696
|
+
Types::ARRAY_QCHAR => Types::QCHAR,
|
697
|
+
Types::ARRAY_REAL => Types::REAL,
|
698
|
+
Types::ARRAY_SMALLINT => Types::SMALLINT,
|
699
|
+
Types::ARRAY_TEXT => Types::TEXT,
|
700
|
+
Types::ARRAY_TIME => Types::TIME,
|
701
|
+
Types::ARRAY_TIMESTAMP => Types::TIMESTAMP,
|
702
|
+
Types::ARRAY_TIMESTAMP_WITH_TIME_ZONE => Types::TIMESTAMP_WITH_TIME_ZONE,
|
703
|
+
Types::ARRAY_TIME_WITH_TIME_ZONE => Types::TIME_WITH_TIME_ZONE,
|
704
|
+
Types::ARRAY_VARBIT => Types::VARBIT,
|
705
|
+
Types::ARRAY_VARCHAR => Types::VARCHAR,
|
706
|
+
}
|
707
|
+
|
708
|
+
AutoConverter = proc { |s, type_code|
|
709
|
+
array_element_type = ARRAY_ELEMENT_TYPES[type_code]
|
710
|
+
if !array_element_type.nil?
|
711
|
+
s = Translate.sql_to_array(s)
|
712
|
+
type_code = array_element_type
|
713
|
+
end
|
714
|
+
converter = CONVERTERS[type_code] || StringConverter
|
715
|
+
Translate.deep_collect(s) do |e|
|
716
|
+
converter.call(e)
|
717
|
+
end
|
718
|
+
}
|
719
|
+
|
720
|
+
def data_type_for(value, as)
|
721
|
+
return nil if as.nil?
|
722
|
+
if value.is_a?(Float)
|
723
|
+
'float'
|
724
|
+
elsif value.is_a?(Integer)
|
725
|
+
'integer'
|
726
|
+
elsif value == true || value == false
|
727
|
+
'boolean'
|
728
|
+
elsif value.is_a?(Time)
|
729
|
+
'time'
|
730
|
+
else
|
731
|
+
'string'
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
def distinct_clause
|
736
|
+
if @distinct
|
737
|
+
" distinct"
|
738
|
+
elsif !@distinct_on.empty?
|
739
|
+
" distinct on (#{@distinct_on.join(', ')})"
|
740
|
+
else
|
741
|
+
""
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
745
|
+
def add_set_op(op, select)
|
746
|
+
@set_ops << [op, select.statement]
|
747
|
+
end
|
748
|
+
|
749
|
+
def expression_list
|
750
|
+
@columns.collect do |column|
|
751
|
+
[column.value, column.as].compact.join(' as ')
|
752
|
+
end.join(', ')
|
753
|
+
end
|
754
|
+
|
755
|
+
def tableExpression
|
756
|
+
if @tables.empty?
|
757
|
+
""
|
758
|
+
else
|
759
|
+
" from #{@tables.join(', ')}"
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
def join_clause
|
764
|
+
if @joins.empty?
|
765
|
+
""
|
766
|
+
else
|
767
|
+
" " + @joins.join(' ')
|
768
|
+
end
|
769
|
+
end
|
770
|
+
|
771
|
+
def set_ops_clause
|
772
|
+
if @set_ops.empty?
|
773
|
+
""
|
774
|
+
else
|
775
|
+
' ' + @set_ops.collect do |op, statement|
|
776
|
+
"#{op} (#{statement})"
|
777
|
+
end.join(' ')
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
781
|
+
def random_order?
|
782
|
+
ENV['RANDOM_SQL_ORDER'] && !@distinct && @distinct_on.empty? && @set_ops.empty?
|
783
|
+
end
|
784
|
+
|
785
|
+
def order_by_clause
|
786
|
+
order = @order_by.dup
|
787
|
+
order << 'random()' if random_order?
|
788
|
+
if order.empty?
|
789
|
+
""
|
790
|
+
else
|
791
|
+
" order by " + order.join(', ')
|
792
|
+
end
|
793
|
+
end
|
794
|
+
|
795
|
+
def limit_clause
|
796
|
+
if @limit.nil?
|
797
|
+
""
|
798
|
+
else
|
799
|
+
" limit #{@limit}"
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
803
|
+
def offset_clause
|
804
|
+
if @offset.nil?
|
805
|
+
""
|
806
|
+
else
|
807
|
+
" offset #{@offset}"
|
808
|
+
end
|
809
|
+
end
|
810
|
+
|
811
|
+
def for_update_clause
|
812
|
+
if @for_update
|
813
|
+
" for update"
|
814
|
+
else
|
815
|
+
""
|
816
|
+
end
|
817
|
+
end
|
818
|
+
|
819
|
+
def where_clause
|
820
|
+
if @where.empty?
|
821
|
+
""
|
822
|
+
else
|
823
|
+
" where " + @where.join(' and ')
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
827
|
+
def group_by_clause
|
828
|
+
if @group_by.empty?
|
829
|
+
""
|
830
|
+
else
|
831
|
+
" group by " + @group_by.join(', ')
|
832
|
+
end
|
833
|
+
end
|
834
|
+
|
835
|
+
def having_clause
|
836
|
+
if @having.empty?
|
837
|
+
""
|
838
|
+
else
|
839
|
+
" having " + @having.join(' and ')
|
840
|
+
end
|
841
|
+
end
|
842
|
+
|
843
|
+
def query_and_translate(connection, statement)
|
844
|
+
connection.exec_and_translate(statement, @columns)
|
845
|
+
end
|
846
|
+
|
847
|
+
end
|
848
|
+
|
849
|
+
end
|
850
|
+
|
851
|
+
# Local Variables:
|
852
|
+
# tab-width: 2
|
853
|
+
# ruby-indent-level: 2
|
854
|
+
# indent-tabs-mode: nil
|
855
|
+
# End:
|