github-ds 0.1.0
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 +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +33 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +201 -0
- data/Rakefile +10 -0
- data/examples/example_setup.rb +38 -0
- data/examples/kv.rb +45 -0
- data/examples/result.rb +50 -0
- data/examples/sql.rb +44 -0
- data/examples/sql_add.rb +42 -0
- data/examples/sql_with_connection.rb +32 -0
- data/github-ds.gemspec +42 -0
- data/lib/generators/github/ds/active_record_generator.rb +22 -0
- data/lib/generators/github/ds/templates/migration.rb +19 -0
- data/lib/github-ds.rb +1 -0
- data/lib/github/ds.rb +8 -0
- data/lib/github/ds/version.rb +5 -0
- data/lib/github/kv.rb +343 -0
- data/lib/github/result.rb +229 -0
- data/lib/github/sql.rb +447 -0
- data/script/bootstrap +6 -0
- data/script/console +14 -0
- data/script/install +6 -0
- data/script/release +6 -0
- data/script/test +6 -0
- metadata +202 -0
@@ -0,0 +1,229 @@
|
|
1
|
+
module GitHub
|
2
|
+
class Result
|
3
|
+
# Invokes the supplied block and wraps the return value in a
|
4
|
+
# GitHub::Result object.
|
5
|
+
#
|
6
|
+
# Exceptions raised by the block are caught and also wrapped.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# GitHub::Result.new { 123 }
|
11
|
+
# # => #<GitHub::Result value: 123>
|
12
|
+
#
|
13
|
+
# GitHub::Result.new { raise "oops" }
|
14
|
+
# # => #<GitHub::Result error: #<RuntimeError: oops>>
|
15
|
+
#
|
16
|
+
def initialize
|
17
|
+
begin
|
18
|
+
@value = yield
|
19
|
+
@error = nil
|
20
|
+
rescue => e
|
21
|
+
@error = e
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
if ok?
|
27
|
+
"#<GitHub::Result:0x%x value: %s>" % [object_id, @value.inspect]
|
28
|
+
else
|
29
|
+
"#<GitHub::Result:0x%x error: %s>" % [object_id, @error.inspect]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :inspect, :to_s
|
34
|
+
|
35
|
+
# If the result represents a value, invokes the supplied block with
|
36
|
+
# that value.
|
37
|
+
#
|
38
|
+
# If the result represents an error, returns self.
|
39
|
+
#
|
40
|
+
# The block must also return a GitHub::Result object.
|
41
|
+
# Use #map otherwise.
|
42
|
+
#
|
43
|
+
# Example:
|
44
|
+
#
|
45
|
+
# result = do_something().then { |val|
|
46
|
+
# do_other_thing(val)
|
47
|
+
# }
|
48
|
+
# # => #<GitHub::Result value: ...>
|
49
|
+
#
|
50
|
+
# do_something_that_fails().then { |val|
|
51
|
+
# # never invoked
|
52
|
+
# }
|
53
|
+
# # => #<GitHub::Result error: ...>
|
54
|
+
#
|
55
|
+
def then
|
56
|
+
if ok?
|
57
|
+
result = yield(@value)
|
58
|
+
raise TypeError, "block invoked in GitHub::Result#then did not return GitHub::Result" unless result.is_a?(Result)
|
59
|
+
result
|
60
|
+
else
|
61
|
+
self
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# If the result represents an error, invokes the supplied block with that error.
|
66
|
+
#
|
67
|
+
# If the result represents a value, returns self.
|
68
|
+
#
|
69
|
+
# The block must also return a GitHub::Result object.
|
70
|
+
# Use #map otherwise.
|
71
|
+
#
|
72
|
+
# Example:
|
73
|
+
#
|
74
|
+
# result = do_something().rescue { |val|
|
75
|
+
# # never invoked
|
76
|
+
# }
|
77
|
+
# # => #<GitHub::Result value: ...>
|
78
|
+
#
|
79
|
+
# do_something_that_fails().rescue { |val|
|
80
|
+
# # handle_error(val)
|
81
|
+
# }
|
82
|
+
# # => #<GitHub::Result error: ...>
|
83
|
+
#
|
84
|
+
def rescue
|
85
|
+
return self if ok?
|
86
|
+
result = yield(@error)
|
87
|
+
raise TypeError, "block invoked in GitHub::Result#rescue did not return GitHub::Result" unless result.is_a?(Result)
|
88
|
+
result
|
89
|
+
end
|
90
|
+
|
91
|
+
# If the result represents a value, invokes the supplied block with that
|
92
|
+
# value and wraps the block's return value in a GitHub::Result.
|
93
|
+
#
|
94
|
+
# If the result represents an error, returns self.
|
95
|
+
#
|
96
|
+
# The block should not return a GitHub::Result object (unless you
|
97
|
+
# truly intend to create a GitHub::Result<GitHub::Result<T>>).
|
98
|
+
# Use #then if it does.
|
99
|
+
#
|
100
|
+
# Example:
|
101
|
+
#
|
102
|
+
# result = do_something()
|
103
|
+
# # => #<GitHub::Result value: 123>
|
104
|
+
#
|
105
|
+
# result.map { |val| val * 2 }
|
106
|
+
# # => #<GitHub::Result value: 246>
|
107
|
+
#
|
108
|
+
# do_something_that_fails().map { |val|
|
109
|
+
# # never invoked
|
110
|
+
# }
|
111
|
+
# # => #<GitHub::Result error: ...>
|
112
|
+
#
|
113
|
+
def map
|
114
|
+
if ok?
|
115
|
+
Result.new { yield(@value) }
|
116
|
+
else
|
117
|
+
self
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# If the result represents a value, returns that value.
|
122
|
+
#
|
123
|
+
# If the result represents an error, invokes the supplied block with the
|
124
|
+
# exception object.
|
125
|
+
#
|
126
|
+
# Example:
|
127
|
+
#
|
128
|
+
# result = do_something()
|
129
|
+
# # => #<GitHub::Result value: "foo">
|
130
|
+
#
|
131
|
+
# result.value { "nope" }
|
132
|
+
# # => "foo"
|
133
|
+
#
|
134
|
+
# result = do_something_that_fails()
|
135
|
+
# # => #<GitHub::Result error: ...>
|
136
|
+
#
|
137
|
+
# result.value { "nope" }
|
138
|
+
# # => #<GitHub::Result value: "nope">
|
139
|
+
#
|
140
|
+
def value
|
141
|
+
unless block_given?
|
142
|
+
raise ArgumentError, "must provide a block to GitHub::Result#value to be invoked in case of error"
|
143
|
+
end
|
144
|
+
|
145
|
+
if ok?
|
146
|
+
@value
|
147
|
+
else
|
148
|
+
yield(@error)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# If the result represents a value, returns that value.
|
153
|
+
#
|
154
|
+
# If the result represents an error, raises that error.
|
155
|
+
#
|
156
|
+
# Example:
|
157
|
+
#
|
158
|
+
# result = do_something()
|
159
|
+
# # => #<GitHub::Result value: "foo">
|
160
|
+
#
|
161
|
+
# result.value!
|
162
|
+
# # => "foo"
|
163
|
+
#
|
164
|
+
# result = do_something_that_fails()
|
165
|
+
# # => #<GitHub::Result error: ...>
|
166
|
+
#
|
167
|
+
# result.value!
|
168
|
+
# # !! raises exception
|
169
|
+
#
|
170
|
+
def value!
|
171
|
+
if ok?
|
172
|
+
@value
|
173
|
+
else
|
174
|
+
raise @error
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Returns true if the result represents a value, false if an error.
|
179
|
+
#
|
180
|
+
# Example:
|
181
|
+
#
|
182
|
+
# result = do_something()
|
183
|
+
# # => #<GitHub::Result value: "foo">
|
184
|
+
#
|
185
|
+
# result.ok?
|
186
|
+
# # => true
|
187
|
+
#
|
188
|
+
# result = do_something_that_fails()
|
189
|
+
# # => #<GitHub::Result error: ...>
|
190
|
+
#
|
191
|
+
# result.ok?
|
192
|
+
# # => false
|
193
|
+
#
|
194
|
+
def ok?
|
195
|
+
!@error
|
196
|
+
end
|
197
|
+
|
198
|
+
# If the result represents a value, returns nil.
|
199
|
+
#
|
200
|
+
# If the result represents an error, returns that error.
|
201
|
+
#
|
202
|
+
# result = do_something()
|
203
|
+
# # => #<GitHub::Result value: "foo">
|
204
|
+
#
|
205
|
+
# result.error
|
206
|
+
# # => nil
|
207
|
+
#
|
208
|
+
# result = do_something_that_fails()
|
209
|
+
# # => #<GitHub::Result error: ...>
|
210
|
+
#
|
211
|
+
# result.error
|
212
|
+
# # => ...
|
213
|
+
#
|
214
|
+
def error
|
215
|
+
@error
|
216
|
+
end
|
217
|
+
|
218
|
+
# Create a GitHub::Result with only the error condition set.
|
219
|
+
#
|
220
|
+
# GitHub::Result.error(e)
|
221
|
+
# # => # <GitHub::Result error: ...>
|
222
|
+
#
|
223
|
+
def self.error(e)
|
224
|
+
result = allocate
|
225
|
+
result.instance_variable_set(:@error, e)
|
226
|
+
result
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
data/lib/github/sql.rb
ADDED
@@ -0,0 +1,447 @@
|
|
1
|
+
require "active_record"
|
2
|
+
|
3
|
+
module GitHub
|
4
|
+
# Public: Build and execute a SQL query, returning results as Arrays. This
|
5
|
+
# class uses ActiveRecord's connection classes, but provides a better API for
|
6
|
+
# bind values and raw data access.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# sql = GitHub::SQL.new(<<-SQL, :parent_ids => parent_ids, :network_id => network_id)
|
11
|
+
# SELECT * FROM repositories
|
12
|
+
# WHERE source_id = :network_id AND parent_id IN :parent_ids
|
13
|
+
# SQL
|
14
|
+
# sql.results
|
15
|
+
# => returns an Array of Arrays, one for each row
|
16
|
+
# sql.hash_results
|
17
|
+
# => returns an Array of Hashes instead
|
18
|
+
#
|
19
|
+
# Things to be aware of:
|
20
|
+
#
|
21
|
+
# * `nil` is always considered an error and not a usable value. If you need a
|
22
|
+
# SQL NULL, use the NULL constant instead.
|
23
|
+
#
|
24
|
+
# * Identical column names in SELECTs will be overridden:
|
25
|
+
# `SELECT t1.id, t2.id FROM...` will only return one value for `id`. To get
|
26
|
+
# more than one column of the same name, use aliases:
|
27
|
+
# `SELECT t1.id t1_id, t2.id t2_id FROM ...`
|
28
|
+
#
|
29
|
+
# * Arrays are escaped as `(item, item, item)`. If you need to insert multiple
|
30
|
+
# rows (Arrays of Arrays), you must specify the bind value using
|
31
|
+
# GitHub::SQL::ROWS(array_of_arrays).
|
32
|
+
#
|
33
|
+
class SQL
|
34
|
+
|
35
|
+
# Internal: a SQL literal value.
|
36
|
+
class Literal
|
37
|
+
# Public: the string value of this literal
|
38
|
+
attr_reader :value
|
39
|
+
|
40
|
+
def initialize(value)
|
41
|
+
@value = value.to_s.dup.freeze
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
"<#{self.class.name} #{value}>"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Internal: a list of arrays of values for insertion into SQL.
|
50
|
+
class Rows
|
51
|
+
# Public: the Array of row values
|
52
|
+
attr_reader :values
|
53
|
+
|
54
|
+
def initialize(values)
|
55
|
+
unless values.all? { |v| v.is_a? Array }
|
56
|
+
raise ArgumentError, "cannot instantiate SQL rows with anything but arrays"
|
57
|
+
end
|
58
|
+
@values = values.dup.freeze
|
59
|
+
end
|
60
|
+
|
61
|
+
def inspect
|
62
|
+
"<#{self.class.name} #{values.inspect}>"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Public: Instantiate a literal SQL value.
|
67
|
+
#
|
68
|
+
# WARNING: The given value is LITERALLY inserted into your SQL without being
|
69
|
+
# escaped, so use this with extreme caution.
|
70
|
+
def self.LITERAL(string)
|
71
|
+
Literal.new(string)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Public: Escape a binary SQL value
|
75
|
+
#
|
76
|
+
# Used when a column contains binary data which needs to be escaped
|
77
|
+
# to prevent warnings from MySQL
|
78
|
+
def self.BINARY(string)
|
79
|
+
GitHub::SQL.LITERAL(GitHub::SQL.BINARY_LITERAL(string))
|
80
|
+
end
|
81
|
+
|
82
|
+
# Public: Escape a binary SQL value, yielding a string which can be used as
|
83
|
+
# a literal in SQL
|
84
|
+
#
|
85
|
+
# Performs the core escaping logic for binary strings in MySQL
|
86
|
+
def self.BINARY_LITERAL(string)
|
87
|
+
"x'#{string.unpack("H*")[0]}'"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Public: Instantiate a list of Arrays of SQL values for insertion.
|
91
|
+
def self.ROWS(rows)
|
92
|
+
Rows.new(rows)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Public: prepackaged literal values.
|
96
|
+
NULL = Literal.new "NULL"
|
97
|
+
NOW = Literal.new "NOW()"
|
98
|
+
|
99
|
+
# Public: A superclass for errors.
|
100
|
+
class Error < RuntimeError
|
101
|
+
end
|
102
|
+
|
103
|
+
# Public: Raised when a bound ":keyword" value isn't available.
|
104
|
+
class BadBind < Error
|
105
|
+
def initialize(keyword)
|
106
|
+
super "There's no bind value for #{keyword.inspect}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Public: Raised when a bound value can't be sanitized.
|
111
|
+
class BadValue < Error
|
112
|
+
def initialize(value, description = nil)
|
113
|
+
description ||= "a #{value.class.name}"
|
114
|
+
super "Can't sanitize #{description}: #{value.inspect}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Internal: A Symbol-Keyed Hash of bind values.
|
119
|
+
attr_reader :binds
|
120
|
+
|
121
|
+
# Public: The SQL String to be executed. Modified in place.
|
122
|
+
attr_reader :query
|
123
|
+
|
124
|
+
# Public: Initialize a new instance.
|
125
|
+
#
|
126
|
+
# query - An initial SQL string (default: "").
|
127
|
+
# binds - A Hash of bind values keyed by Symbol (default: {}). There are
|
128
|
+
# a couple exceptions. If they clash with a bind value, add them
|
129
|
+
# in a later #bind or #add call.
|
130
|
+
#
|
131
|
+
# :connection - An ActiveRecord Connection adapter.
|
132
|
+
# :force_timezone - A Symbol describing the ActiveRecord default
|
133
|
+
# timezone. Either :utc or :local.
|
134
|
+
#
|
135
|
+
def initialize(query = nil, binds = nil)
|
136
|
+
if query.is_a? Hash
|
137
|
+
binds = query
|
138
|
+
query = nil
|
139
|
+
end
|
140
|
+
|
141
|
+
@last_insert_id = nil
|
142
|
+
@affected_rows = nil
|
143
|
+
@binds = binds ? binds.dup : {}
|
144
|
+
@query = ""
|
145
|
+
@connection = @binds.delete :connection
|
146
|
+
@force_tz = @binds.delete :force_timezone
|
147
|
+
|
148
|
+
add query if !query.nil?
|
149
|
+
end
|
150
|
+
|
151
|
+
# Public: Add a chunk of SQL to the query. Any ":keyword" tokens in the SQL
|
152
|
+
# will be replaced with database-safe values from the current binds.
|
153
|
+
#
|
154
|
+
# sql - A String containing a fragment of SQL.
|
155
|
+
# extras - A Hash of bind values keyed by Symbol (default: {}). These bind
|
156
|
+
# values are only be used to interpolate this SQL fragment,and
|
157
|
+
# aren't available to subsequent adds.
|
158
|
+
#
|
159
|
+
# Returns self.
|
160
|
+
# Raises GitHub::SQL::BadBind for unknown keyword tokens.
|
161
|
+
def add(sql, extras = nil)
|
162
|
+
return self if sql.blank?
|
163
|
+
|
164
|
+
query << " " unless query.empty?
|
165
|
+
|
166
|
+
if @force_tz
|
167
|
+
zone = ActiveRecord::Base.default_timezone
|
168
|
+
ActiveRecord::Base.default_timezone = @force_tz
|
169
|
+
end
|
170
|
+
|
171
|
+
query << interpolate(sql.strip, extras)
|
172
|
+
|
173
|
+
self
|
174
|
+
ensure
|
175
|
+
ActiveRecord::Base.default_timezone = zone if @force_tz
|
176
|
+
end
|
177
|
+
|
178
|
+
# Public: Add a chunk of SQL to the query, unless query generated so far is empty.
|
179
|
+
#
|
180
|
+
# Example: use this for conditionally adding UNION when generating sets of SELECTs.
|
181
|
+
#
|
182
|
+
# sql - A String containing a fragment of SQL.
|
183
|
+
# extras - A Hash of bind values keyed by Symbol (default: {}). These bind
|
184
|
+
# values are only be used to interpolate this SQL fragment,and
|
185
|
+
# aren't available to subsequent adds.
|
186
|
+
#
|
187
|
+
# Returns self.
|
188
|
+
# Raises GitHub::SQL::BadBind for unknown keyword tokens.
|
189
|
+
def add_unless_empty(sql, extras = nil)
|
190
|
+
return self if query.empty?
|
191
|
+
add sql, extras
|
192
|
+
end
|
193
|
+
|
194
|
+
# Public: The number of affected rows for this connection.
|
195
|
+
def affected_rows
|
196
|
+
@affected_rows || connection.raw_connection.affected_rows
|
197
|
+
end
|
198
|
+
|
199
|
+
# Public: Add additional bind values to be interpolated each time SQL
|
200
|
+
# is added to the query.
|
201
|
+
#
|
202
|
+
# hash - A Symbol-keyed Hash of new values.
|
203
|
+
#
|
204
|
+
# Returns self.
|
205
|
+
def bind(binds)
|
206
|
+
self.binds.merge! binds
|
207
|
+
self
|
208
|
+
end
|
209
|
+
|
210
|
+
# Internal: The object we use to execute SQL and retrieve results. Defaults
|
211
|
+
# to AR::B.connection, but can be overridden with a ":connection" key when
|
212
|
+
# initializing a new instance.
|
213
|
+
def connection
|
214
|
+
@connection || ActiveRecord::Base.connection
|
215
|
+
end
|
216
|
+
|
217
|
+
# Public: the number of rows found by the query.
|
218
|
+
#
|
219
|
+
# Returns FOUND_ROWS() if a SELECT query included SQL_CALC_FOUND_ROWS.
|
220
|
+
# Raises if SQL_CALC_FOUND_ROWS was not present in the query.
|
221
|
+
def found_rows
|
222
|
+
raise "no SQL_CALC_FOUND_ROWS clause present" unless defined? @found_rows
|
223
|
+
@found_rows
|
224
|
+
end
|
225
|
+
|
226
|
+
# Internal: Replace ":keywords" with sanitized values from binds or extras.
|
227
|
+
def interpolate(sql, extras = nil)
|
228
|
+
sql.gsub(/:[a-z][a-z0-9_]*/) do |raw|
|
229
|
+
sym = raw[1..-1].intern # O.o gensym
|
230
|
+
|
231
|
+
if extras && extras.include?(sym)
|
232
|
+
val = extras[sym]
|
233
|
+
elsif binds.include?(sym)
|
234
|
+
val = binds[sym]
|
235
|
+
end
|
236
|
+
|
237
|
+
raise BadBind.new raw if val.nil?
|
238
|
+
|
239
|
+
sanitize val
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Public: The last inserted ID for this connection.
|
244
|
+
def last_insert_id
|
245
|
+
@last_insert_id || connection.raw_connection.last_insert_id
|
246
|
+
end
|
247
|
+
|
248
|
+
# Public: Map each row to an instance of an ActiveRecord::Base subclass.
|
249
|
+
def models(klass)
|
250
|
+
return @models if defined? @models
|
251
|
+
return [] if frozen?
|
252
|
+
|
253
|
+
# Use select_all to retrieve hashes for each row instead of arrays of values.
|
254
|
+
@models = connection.
|
255
|
+
select_all(query, "#{klass.name} Load via #{self.class.name}").
|
256
|
+
collect! { |record| klass.send :instantiate, record }
|
257
|
+
|
258
|
+
retrieve_found_row_count
|
259
|
+
freeze
|
260
|
+
|
261
|
+
@models
|
262
|
+
end
|
263
|
+
|
264
|
+
# Public: Execute, memoize, and return the results of this query.
|
265
|
+
def results
|
266
|
+
return @results if defined? @results
|
267
|
+
return [] if frozen?
|
268
|
+
|
269
|
+
if @force_tz
|
270
|
+
zone = ActiveRecord::Base.default_timezone
|
271
|
+
ActiveRecord::Base.default_timezone = @force_tz
|
272
|
+
end
|
273
|
+
|
274
|
+
case query
|
275
|
+
when /\ADELETE/i
|
276
|
+
@affected_rows = connection.delete(query, "#{self.class.name} Delete")
|
277
|
+
|
278
|
+
when /\AINSERT/i
|
279
|
+
@last_insert_id = connection.insert(query, "#{self.class.name} Insert")
|
280
|
+
|
281
|
+
when /\AUPDATE/i
|
282
|
+
@affected_rows = connection.update(query, "#{self.class.name} Update")
|
283
|
+
|
284
|
+
when /\ASELECT/i
|
285
|
+
# Why not execute or select_rows? Because select_all hits the query cache.
|
286
|
+
@hash_results = connection.select_all(query, "#{self.class.name} Select")
|
287
|
+
@results = @hash_results.map(&:values)
|
288
|
+
|
289
|
+
else
|
290
|
+
@results = connection.execute(query, "#{self.class.name} Execute").to_a
|
291
|
+
end
|
292
|
+
|
293
|
+
@results ||= []
|
294
|
+
|
295
|
+
retrieve_found_row_count
|
296
|
+
freeze
|
297
|
+
|
298
|
+
@results
|
299
|
+
ensure
|
300
|
+
ActiveRecord::Base.default_timezone = zone if @force_tz
|
301
|
+
end
|
302
|
+
|
303
|
+
# Public: If the query is a SELECT, return an array of hashes instead of an array of arrays.
|
304
|
+
def hash_results
|
305
|
+
results
|
306
|
+
@hash_results || @results
|
307
|
+
end
|
308
|
+
|
309
|
+
# Public: Get first row of results.
|
310
|
+
def row
|
311
|
+
results.first
|
312
|
+
end
|
313
|
+
|
314
|
+
# Public: Execute, ignoring results. This is useful when the results of a
|
315
|
+
# query aren't important, often INSERTs, UPDATEs, or DELETEs.
|
316
|
+
#
|
317
|
+
# sql - An optional SQL string. See GitHub::SQL#add for details.
|
318
|
+
# extras - Optional bind values. See GitHub::SQL#add for details.
|
319
|
+
#
|
320
|
+
# Returns self.
|
321
|
+
def run(sql = nil, extras = nil)
|
322
|
+
add sql, extras if !sql.nil?
|
323
|
+
results
|
324
|
+
|
325
|
+
self
|
326
|
+
end
|
327
|
+
|
328
|
+
# Internal: when a SQL_CALC_FOUND_ROWS clause is present in a SELECT query,
|
329
|
+
# retrieve the FOUND_ROWS() value to get a count of the rows sans any
|
330
|
+
# LIMIT/OFFSET clause.
|
331
|
+
def retrieve_found_row_count
|
332
|
+
if query =~ /\A\s*SELECT\s+SQL_CALC_FOUND_ROWS\s+/i
|
333
|
+
@found_rows = connection.select_value "SELECT FOUND_ROWS()", self.class.name
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# Public: Create and execute a new SQL query, ignoring results.
|
338
|
+
#
|
339
|
+
# sql - A SQL string. See GitHub::SQL#add for details.
|
340
|
+
# bindings - Optional bind values. See GitHub::SQL#add for details.
|
341
|
+
#
|
342
|
+
# Returns self.
|
343
|
+
def self.run(sql, bindings = {})
|
344
|
+
new(sql, bindings).run
|
345
|
+
end
|
346
|
+
|
347
|
+
# Public: Create and execute a new SQL query, returning its hash_result rows.
|
348
|
+
#
|
349
|
+
# sql - A SQL string. See GitHub::SQL#add for details.
|
350
|
+
# bindings - Optional bind values. See GitHub::SQL#add for details.
|
351
|
+
#
|
352
|
+
# Returns an Array of result hashes.
|
353
|
+
def self.hash_results(sql, bindings = {})
|
354
|
+
new(sql, bindings).hash_results
|
355
|
+
end
|
356
|
+
|
357
|
+
# Public: Create and execute a new SQL query, returning its result rows.
|
358
|
+
#
|
359
|
+
# sql - A SQL string. See GitHub::SQL#add for details.
|
360
|
+
# bindings - Optional bind values. See GitHub::SQL#add for details.
|
361
|
+
#
|
362
|
+
# Returns an Array of result arrays.
|
363
|
+
def self.results(sql, bindings = {})
|
364
|
+
new(sql, bindings).results
|
365
|
+
end
|
366
|
+
|
367
|
+
# Public: Create and execute a new SQL query, returning the value of the
|
368
|
+
# first column of the first result row.
|
369
|
+
#
|
370
|
+
# sql - A SQL string. See GitHub::SQL#add for details.
|
371
|
+
# bindings - Optional bind values. See GitHub::SQL#add for details.
|
372
|
+
#
|
373
|
+
# Returns a value or nil.
|
374
|
+
def self.value(sql, bindings = {})
|
375
|
+
new(sql, bindings).value
|
376
|
+
end
|
377
|
+
|
378
|
+
# Public: Create and execute a new SQL query, returning its values.
|
379
|
+
#
|
380
|
+
# sql - A SQL string. See GitHub::SQL#add for details.
|
381
|
+
# bindings - Optional bind values. See GitHub::SQL#add for details.
|
382
|
+
#
|
383
|
+
# Returns an Array of values.
|
384
|
+
def self.values(sql, bindings = {})
|
385
|
+
new(sql, bindings).values
|
386
|
+
end
|
387
|
+
|
388
|
+
# Internal: Make `value` database-safe. Ish.
|
389
|
+
def sanitize(value)
|
390
|
+
case value
|
391
|
+
|
392
|
+
when Integer
|
393
|
+
value.to_s
|
394
|
+
|
395
|
+
when Numeric, String
|
396
|
+
connection.quote value
|
397
|
+
|
398
|
+
when Array
|
399
|
+
raise BadValue.new(value, "an empty array") if value.empty?
|
400
|
+
raise BadValue.new(value, "a nested array") if value.any? { |v| v.is_a? Array }
|
401
|
+
|
402
|
+
"(" + value.map { |v| sanitize v }.join(", ") + ")"
|
403
|
+
|
404
|
+
when Literal
|
405
|
+
value.value
|
406
|
+
|
407
|
+
when Rows # rows for insertion
|
408
|
+
value.values.map { |v| sanitize v }.join(", ")
|
409
|
+
|
410
|
+
when Class
|
411
|
+
connection.quote value.name
|
412
|
+
|
413
|
+
when DateTime, Time, Date
|
414
|
+
connection.quote value.to_s(:db)
|
415
|
+
|
416
|
+
when true
|
417
|
+
connection.quoted_true
|
418
|
+
|
419
|
+
when false
|
420
|
+
connection.quoted_false
|
421
|
+
|
422
|
+
when Symbol
|
423
|
+
connection.quote value.to_s
|
424
|
+
|
425
|
+
else
|
426
|
+
raise BadValue, value
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
# Public: Get the first column of the first row of results.
|
431
|
+
def value
|
432
|
+
row && row.first
|
433
|
+
end
|
434
|
+
|
435
|
+
# Public: Is there a value?
|
436
|
+
def value?
|
437
|
+
!value.nil?
|
438
|
+
end
|
439
|
+
|
440
|
+
# Public: Get first column of every row of results.
|
441
|
+
#
|
442
|
+
# Returns an Array or nil.
|
443
|
+
def values
|
444
|
+
results.map(&:first)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|