sequel 0.4.4.1 → 0.4.4.2
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.
- data/CHANGELOG +10 -0
- data/Rakefile +161 -159
- data/lib/sequel.rb +14 -10
- data/lib/sequel/adapters/adapter_skeleton.rb +2 -1
- data/lib/sequel/adapters/ado.rb +2 -1
- data/lib/sequel/adapters/db2.rb +5 -3
- data/lib/sequel/adapters/dbi.rb +2 -1
- data/lib/sequel/adapters/informix.rb +2 -1
- data/lib/sequel/adapters/jdbc.rb +3 -2
- data/lib/sequel/adapters/mysql.rb +268 -264
- data/lib/sequel/adapters/odbc.rb +7 -2
- data/lib/sequel/adapters/odbc_mssql.rb +1 -1
- data/lib/sequel/adapters/openbase.rb +2 -1
- data/lib/sequel/adapters/oracle.rb +2 -1
- data/lib/sequel/adapters/postgres.rb +32 -16
- data/lib/sequel/adapters/sqlite.rb +7 -6
- data/lib/sequel/array_keys.rb +295 -295
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core_sql.rb +14 -5
- data/lib/sequel/database.rb +4 -4
- data/lib/sequel/dataset.rb +12 -10
- data/lib/sequel/dataset/convenience.rb +10 -8
- data/lib/sequel/dataset/sequelizer.rb +19 -16
- data/lib/sequel/dataset/sql.rb +43 -30
- data/lib/sequel/exceptions.rb +45 -0
- data/lib/sequel/migration.rb +7 -5
- data/lib/sequel/model.rb +1 -1
- data/lib/sequel/model/base.rb +3 -3
- data/lib/sequel/model/hooks.rb +0 -4
- data/lib/sequel/model/record.rb +9 -9
- data/lib/sequel/model/relations.rb +2 -2
- data/lib/sequel/pretty_table.rb +6 -3
- data/lib/sequel/schema/schema_sql.rb +11 -6
- data/lib/sequel/worker.rb +8 -7
- data/spec/adapters/sqlite_spec.rb +3 -3
- data/spec/array_keys_spec.rb +543 -543
- data/spec/connection_pool_spec.rb +6 -3
- data/spec/database_spec.rb +4 -4
- data/spec/dataset_spec.rb +25 -25
- data/spec/migration_spec.rb +1 -1
- data/spec/model_spec.rb +16 -16
- data/spec/sequelizer_spec.rb +7 -7
- data/spec/spec.opts +8 -0
- metadata +5 -5
- data/lib/sequel/error.rb +0 -22
data/lib/sequel/adapters/odbc.rb
CHANGED
@@ -35,10 +35,15 @@ module Sequel
|
|
35
35
|
end
|
36
36
|
|
37
37
|
class Dataset < Sequel::Dataset
|
38
|
+
BOOL_TRUE = '1'.freeze
|
39
|
+
BOOL_FALSE = '0'.freeze
|
40
|
+
|
38
41
|
def literal(v)
|
39
42
|
case v
|
40
|
-
when true
|
41
|
-
|
43
|
+
when true
|
44
|
+
BOOL_TRUE
|
45
|
+
when false
|
46
|
+
BOOL_FALSE
|
42
47
|
else
|
43
48
|
super
|
44
49
|
end
|
@@ -31,7 +31,7 @@ module Sequel
|
|
31
31
|
# ADD TOP to SELECT string for LIMITS
|
32
32
|
if limit = opts[:limit]
|
33
33
|
top = "TOP #{limit} "
|
34
|
-
raise
|
34
|
+
raise Error, "Offset not supported" if opts[:offset]
|
35
35
|
end
|
36
36
|
|
37
37
|
columns = opts[:select]
|
@@ -2,14 +2,23 @@ require 'postgres'
|
|
2
2
|
|
3
3
|
class PGconn
|
4
4
|
# the pure-ruby postgres adapter does not have a quote method.
|
5
|
+
TRUE = 't'.freeze
|
6
|
+
FALSE = 'f'.freeze
|
7
|
+
NULL = 'NULL'.freeze
|
8
|
+
|
5
9
|
unless methods.include?('quote')
|
6
10
|
def self.quote(obj)
|
7
11
|
case obj
|
8
|
-
when true
|
9
|
-
|
10
|
-
when
|
11
|
-
|
12
|
-
|
12
|
+
when true
|
13
|
+
TRUE
|
14
|
+
when false
|
15
|
+
FALSE
|
16
|
+
when nil
|
17
|
+
NULL
|
18
|
+
when String
|
19
|
+
"'#{obj}'"
|
20
|
+
else
|
21
|
+
obj.to_s
|
13
22
|
end
|
14
23
|
end
|
15
24
|
end
|
@@ -125,10 +134,13 @@ class PGconn
|
|
125
134
|
end
|
126
135
|
|
127
136
|
class String
|
137
|
+
POSTGRES_BOOL_TRUE = 't'.freeze
|
138
|
+
POSTGRES_BOOL_FALSE = 'f'.freeze
|
139
|
+
|
128
140
|
def postgres_to_bool
|
129
|
-
if self ==
|
141
|
+
if self == POSTGRES_BOOL_TRUE
|
130
142
|
true
|
131
|
-
elsif self ==
|
143
|
+
elsif self == POSTGRES_BOOL_FALSE
|
132
144
|
false
|
133
145
|
else
|
134
146
|
nil
|
@@ -227,16 +239,16 @@ module Sequel
|
|
227
239
|
# An error could occur if the inserted values include a primary key
|
228
240
|
# value, while the primary key is serial.
|
229
241
|
if e.message =~ RE_CURRVAL_ERROR
|
230
|
-
raise
|
242
|
+
raise Error, "Could not return primary key value for the inserted record. Are you specifying a primary key value for a serial primary key?"
|
231
243
|
else
|
232
244
|
raise e
|
233
245
|
end
|
234
246
|
end
|
235
247
|
|
236
248
|
case values
|
237
|
-
when Hash
|
249
|
+
when Hash
|
238
250
|
values[primary_key_for_table(conn, table)]
|
239
|
-
when Array
|
251
|
+
when Array
|
240
252
|
values.first
|
241
253
|
else
|
242
254
|
nil
|
@@ -283,7 +295,7 @@ module Sequel
|
|
283
295
|
rescue => e
|
284
296
|
@logger.info(SQL_ROLLBACK) if @logger
|
285
297
|
conn.async_exec(SQL_ROLLBACK) rescue nil
|
286
|
-
raise e unless
|
298
|
+
raise e unless Error::Rollback === e
|
287
299
|
ensure
|
288
300
|
conn.transaction_in_progress = nil
|
289
301
|
end
|
@@ -303,8 +315,10 @@ module Sequel
|
|
303
315
|
class Dataset < Sequel::Dataset
|
304
316
|
def literal(v)
|
305
317
|
case v
|
306
|
-
when LiteralString
|
307
|
-
|
318
|
+
when LiteralString
|
319
|
+
v
|
320
|
+
when String, Fixnum, Float, TrueClass, FalseClass
|
321
|
+
PGconn.quote(v)
|
308
322
|
else
|
309
323
|
super
|
310
324
|
end
|
@@ -312,7 +326,7 @@ module Sequel
|
|
312
326
|
|
313
327
|
def match_expr(l, r)
|
314
328
|
case r
|
315
|
-
when Regexp
|
329
|
+
when Regexp
|
316
330
|
r.casefold? ? \
|
317
331
|
"(#{literal(l)} ~* #{literal(r.source)})" :
|
318
332
|
"(#{literal(l)} ~ #{literal(r.source)})"
|
@@ -328,8 +342,10 @@ module Sequel
|
|
328
342
|
row_lock_mode = opts ? opts[:lock] : @opts[:lock]
|
329
343
|
sql = super
|
330
344
|
case row_lock_mode
|
331
|
-
when :update
|
332
|
-
|
345
|
+
when :update
|
346
|
+
sql << FOR_UPDATE
|
347
|
+
when :share
|
348
|
+
sql << FOR_SHARE
|
333
349
|
end
|
334
350
|
sql
|
335
351
|
end
|
@@ -71,7 +71,7 @@ module Sequel
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def auto_vacuum=(value)
|
74
|
-
value = AUTO_VACUUM.index(value) || (raise
|
74
|
+
value = AUTO_VACUUM.index(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
|
75
75
|
pragma_set(:auto_vacuum, value)
|
76
76
|
end
|
77
77
|
|
@@ -82,7 +82,7 @@ module Sequel
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def synchronous=(value)
|
85
|
-
value = SYNCHRONOUS.index(value) || (raise
|
85
|
+
value = SYNCHRONOUS.index(value) || (raise Error, "Invalid value for synchronous option. Please specify one of :off, :normal, :full.")
|
86
86
|
pragma_set(:synchronous, value)
|
87
87
|
end
|
88
88
|
|
@@ -93,7 +93,7 @@ module Sequel
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def temp_store=(value)
|
96
|
-
value = TEMP_STORE.index(value) || (raise
|
96
|
+
value = TEMP_STORE.index(value) || (raise Error, "Invalid value for temp_store option. Please specify one of :default, :file, :memory.")
|
97
97
|
pragma_set(:temp_store, value)
|
98
98
|
end
|
99
99
|
|
@@ -102,7 +102,7 @@ module Sequel
|
|
102
102
|
when :add_column
|
103
103
|
"ALTER TABLE #{table} ADD #{column_definition_sql(op)}"
|
104
104
|
else
|
105
|
-
raise
|
105
|
+
raise Error, "Unsupported ALTER TABLE operation"
|
106
106
|
end
|
107
107
|
end
|
108
108
|
|
@@ -116,7 +116,7 @@ module Sequel
|
|
116
116
|
conn.transaction {result = yield(conn)}
|
117
117
|
result
|
118
118
|
rescue => e
|
119
|
-
raise e unless
|
119
|
+
raise e unless Error::Rollback === e
|
120
120
|
end
|
121
121
|
end
|
122
122
|
end
|
@@ -125,7 +125,8 @@ module Sequel
|
|
125
125
|
class Dataset < Sequel::Dataset
|
126
126
|
def literal(v)
|
127
127
|
case v
|
128
|
-
when Time
|
128
|
+
when Time
|
129
|
+
literal(v.iso8601)
|
129
130
|
else
|
130
131
|
super
|
131
132
|
end
|
data/lib/sequel/array_keys.rb
CHANGED
@@ -1,296 +1,296 @@
|
|
1
|
-
# ArrayKeys provide support for accessing array elements by keys. ArrayKeys are
|
2
|
-
# based on the arrayfields gem by Ara Howard, and can be used as substitutes
|
3
|
-
# for fetching records tuples as Ruby hashes.
|
4
|
-
#
|
5
|
-
# The main advantage offered by ArrayKeys over hashes is that the values are
|
6
|
-
# always ordered according to the column order in the query. Another purported
|
7
|
-
# advantage is that they reduce the memory footprint, but this has turned out
|
8
|
-
# to be a false claim.
|
9
|
-
module ArrayKeys
|
10
|
-
# The KeySet module contains methods that extend an array of keys to return
|
11
|
-
# a key's position in the key set.
|
12
|
-
module KeySet
|
13
|
-
# Returns the key's position in the key set. Provides indifferent access
|
14
|
-
# for symbols and strings.
|
15
|
-
def key_pos(key)
|
16
|
-
@key_indexes ||= inject({}) {|h, k| h[k.to_sym] = h.size; h}
|
17
|
-
@key_indexes[key] || @key_indexes[key.to_sym] || @key_indexes[key.to_s]
|
18
|
-
end
|
19
|
-
|
20
|
-
# Adds a key to the key set.
|
21
|
-
def add_key(key)
|
22
|
-
self << key
|
23
|
-
@key_indexes[key] = @key_indexes.size
|
24
|
-
end
|
25
|
-
|
26
|
-
# Removes a key from the key set by its index.
|
27
|
-
def del_key(idx)
|
28
|
-
delete_at(idx)
|
29
|
-
@key_indexes = nil # reset key indexes
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# The KeyAccess provides a large part of the Hash API for arrays with keys.
|
34
|
-
module KeyAccess
|
35
|
-
# Returns a value referenced by an array index or a key.
|
36
|
-
def [](idx, *args)
|
37
|
-
if String === idx or Symbol === idx
|
38
|
-
(idx = @keys.key_pos(idx)) ? super(idx, *args) : nil
|
39
|
-
else
|
40
|
-
super
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# Sets the value referenced by an array index or a key.
|
45
|
-
def []=(idx,*args)
|
46
|
-
if String === idx or Symbol === idx
|
47
|
-
idx = @keys.key_pos(idx) || @keys.add_key(idx.to_sym)
|
48
|
-
end
|
49
|
-
super(idx, *args)
|
50
|
-
end
|
51
|
-
|
52
|
-
# Stores a value by index or key.
|
53
|
-
def store(k, v); self[k] = v; end
|
54
|
-
|
55
|
-
# Slices the array, and returns an array with its keys sliced accordingly.
|
56
|
-
def slice(*args)
|
57
|
-
s = super(*args)
|
58
|
-
s.keys = @keys.slice(*args)
|
59
|
-
s
|
60
|
-
end
|
61
|
-
|
62
|
-
# Converts the array into a hash.
|
63
|
-
def to_hash
|
64
|
-
h = {}
|
65
|
-
each_with_index {|v, i| h[@keys[i].to_sym] = v}
|
66
|
-
h
|
67
|
-
end
|
68
|
-
alias_method :to_h, :to_hash
|
69
|
-
|
70
|
-
# Iterates over each key-value pair in the array.
|
71
|
-
def each_pair
|
72
|
-
each_with_index {|v, i| yield @keys[i], v}
|
73
|
-
end
|
74
|
-
|
75
|
-
# Iterates over the array's associated keys.
|
76
|
-
def each_key(&block)
|
77
|
-
@keys.each(&block)
|
78
|
-
end
|
79
|
-
|
80
|
-
# Iterates over the array's values.
|
81
|
-
def each_value(&block)
|
82
|
-
each(&block)
|
83
|
-
end
|
84
|
-
|
85
|
-
# Deletes a value by its key.
|
86
|
-
def delete(key, *args)
|
87
|
-
if (idx = @keys.key_pos(key))
|
88
|
-
delete_at(idx)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
# Deletes a value by its index.
|
93
|
-
def delete_at(idx)
|
94
|
-
super(idx)
|
95
|
-
@keys = @keys.clone
|
96
|
-
@keys.del_key(idx)
|
97
|
-
end
|
98
|
-
|
99
|
-
# Returns true if the array's key set contains the given key.
|
100
|
-
def include?(k)
|
101
|
-
@keys.include?(k) || @keys.include?(k.to_sym) || @keys.include?(k.to_s)
|
102
|
-
end
|
103
|
-
|
104
|
-
# Returns true if the array's key set contains the given key.
|
105
|
-
def has_key?(k)
|
106
|
-
@keys.include?(k) || @keys.include?(k.to_sym) || @keys.include?(k.to_s)
|
107
|
-
end
|
108
|
-
alias_method :member?, :has_key?
|
109
|
-
alias_method :key?, :has_key?
|
110
|
-
|
111
|
-
# Returns true if the array contains the given value.
|
112
|
-
def has_value?(k); orig_include?(k); end
|
113
|
-
alias_method :value?, :has_value?
|
114
|
-
|
115
|
-
# Fetches a value by its key and optionally passes it through the given
|
116
|
-
# block:
|
117
|
-
#
|
118
|
-
# row.fetch(:name) {|v| v.to_sym}
|
119
|
-
#
|
120
|
-
# You can also give a default value
|
121
|
-
#
|
122
|
-
# row.fetch(:name, 'untitled')
|
123
|
-
#
|
124
|
-
def fetch(k, *args, &block)
|
125
|
-
if idx = @keys.key_pos(k)
|
126
|
-
v = at idx
|
127
|
-
else
|
128
|
-
!args.empty? ? (v = args.first) : (raise IndexError, "key not found")
|
129
|
-
end
|
130
|
-
block ? block[v] : v
|
131
|
-
end
|
132
|
-
|
133
|
-
# Returns self.
|
134
|
-
def values
|
135
|
-
self
|
136
|
-
end
|
137
|
-
|
138
|
-
# Creates a copy of self with the same key set.
|
139
|
-
def dup
|
140
|
-
copy = super
|
141
|
-
copy.keys = @keys
|
142
|
-
copy
|
143
|
-
end
|
144
|
-
|
145
|
-
# Creates a copy of self with a copy of the key set.
|
146
|
-
def clone
|
147
|
-
copy = super
|
148
|
-
copy.keys = @keys.clone
|
149
|
-
copy
|
150
|
-
end
|
151
|
-
|
152
|
-
# Returns an array merged from self and the given array.
|
153
|
-
def merge(values, &block)
|
154
|
-
clone.merge!(values, &block)
|
155
|
-
end
|
156
|
-
|
157
|
-
# Merges the given array with self, optionally passing the values from self
|
158
|
-
# through the given block:
|
159
|
-
#
|
160
|
-
# row.merge!(new_values) {|k, old, new| (k == :name) ? old : new}
|
161
|
-
#
|
162
|
-
def merge!(values, &block)
|
163
|
-
values.each_pair do |k, v|
|
164
|
-
self[k] = (has_key?(k) && block) ? block[k, self[k], v] : v
|
165
|
-
end
|
166
|
-
self
|
167
|
-
end
|
168
|
-
alias_method :update, :merge!
|
169
|
-
alias_method :update!, :merge!
|
170
|
-
end
|
171
|
-
|
172
|
-
# The ArrayExtensions module provides extensions for the Array class.
|
173
|
-
module ArrayExtensions
|
174
|
-
attr_reader :keys
|
175
|
-
|
176
|
-
# Sets the key set for the array. Once a key set has been set for an array,
|
177
|
-
# it is extended with the KeyAccess API
|
178
|
-
def keys=(keys)
|
179
|
-
extend ArrayKeys::KeyAccess if keys
|
180
|
-
@keys = keys.frozen? ? keys.dup : keys
|
181
|
-
unless @keys.respond_to?(:key_pos)
|
182
|
-
@keys.extend(ArrayKeys::KeySet)
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
alias_method :columns, :keys
|
187
|
-
alias_method :columns=, :keys=
|
188
|
-
end
|
189
|
-
|
190
|
-
# The DatasetExtensions module provides extensions that modify
|
191
|
-
# a dataset to return Array tuples instead of Hash tuples.
|
192
|
-
module DatasetExtensions
|
193
|
-
# Fetches a dataset's records, converting each tuple into an array with
|
194
|
-
# keys.
|
195
|
-
def array_tuples_each(opts = nil, &block)
|
196
|
-
fetch_rows(select_sql(opts)) {|h| block[Array.from_hash(h)]}
|
197
|
-
end
|
198
|
-
|
199
|
-
# Provides the corresponding behavior to Sequel::Dataset#update_each_method,
|
200
|
-
# using array tuples.
|
201
|
-
def array_tuples_update_each_method
|
202
|
-
# warning: ugly code generation ahead
|
203
|
-
if @row_proc && @transform
|
204
|
-
class << self
|
205
|
-
def each(opts = nil, &block)
|
206
|
-
if opts && opts[:naked]
|
207
|
-
fetch_rows(select_sql(opts)) {|r| block[transform_load(Array.from_hash(r))]}
|
208
|
-
else
|
209
|
-
fetch_rows(select_sql(opts)) {|r| block[@row_proc[transform_load(Array.from_hash(r))]]}
|
210
|
-
end
|
211
|
-
self
|
212
|
-
end
|
213
|
-
end
|
214
|
-
elsif @row_proc
|
215
|
-
class << self
|
216
|
-
def each(opts = nil, &block)
|
217
|
-
if opts && opts[:naked]
|
218
|
-
fetch_rows(select_sql(opts)) {|r| block[Array.from_hash(r)]}
|
219
|
-
else
|
220
|
-
fetch_rows(select_sql(opts)) {|r| block[@row_proc[Array.from_hash(r)]]}
|
221
|
-
end
|
222
|
-
self
|
223
|
-
end
|
224
|
-
end
|
225
|
-
elsif @transform
|
226
|
-
class << self
|
227
|
-
def each(opts = nil, &block)
|
228
|
-
fetch_rows(select_sql(opts)) {|r| block[transform_load(Array.from_hash(r))]}
|
229
|
-
self
|
230
|
-
end
|
231
|
-
end
|
232
|
-
else
|
233
|
-
class << self
|
234
|
-
def each(opts = nil, &block)
|
235
|
-
fetch_rows(select_sql(opts)) {|r| block[Array.from_hash(r)]}
|
236
|
-
self
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
# Array extensions.
|
245
|
-
class Array
|
246
|
-
alias_method :orig_include?, :include?
|
247
|
-
|
248
|
-
include ArrayKeys::ArrayExtensions
|
249
|
-
|
250
|
-
# Converts a hash into an array with keys.
|
251
|
-
def self.from_hash(h)
|
252
|
-
a = []; a.keys = []
|
253
|
-
a.merge!(h)
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
module Sequel
|
258
|
-
# Modifies all dataset classes to fetch records as arrays with keys. By
|
259
|
-
# default records are fetched as hashes.
|
260
|
-
def self.use_array_tuples
|
261
|
-
Dataset.dataset_classes.each do |c|
|
262
|
-
c.class_eval do
|
263
|
-
if method_defined?(:array_tuples_fetch_rows)
|
264
|
-
alias_method :hash_tuples_fetch_rows, :fetch_rows
|
265
|
-
alias_method :fetch_rows, :array_tuples_fetch_rows
|
266
|
-
else
|
267
|
-
alias_method :orig_each, :each
|
268
|
-
alias_method :orig_update_each_method, :update_each_method
|
269
|
-
include ArrayKeys::DatasetExtensions
|
270
|
-
alias_method :each, :array_tuples_each
|
271
|
-
alias_method :update_each_method, :array_tuples_update_each_method
|
272
|
-
end
|
273
|
-
end
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
# Modifies all dataset classes to fetch records as hashes.
|
278
|
-
def self.use_hash_tuples
|
279
|
-
Dataset.dataset_classes.each do |c|
|
280
|
-
c.class_eval do
|
281
|
-
if method_defined?(:hash_tuples_fetch_rows)
|
282
|
-
alias_method :fetch_rows, :hash_tuples_fetch_rows
|
283
|
-
else
|
284
|
-
if method_defined?(:orig_each)
|
285
|
-
alias_method :each, :orig_each
|
286
|
-
undef_method :orig_each
|
287
|
-
end
|
288
|
-
if method_defined?(:orig_update_each_method)
|
289
|
-
alias_method :update_each_method, :orig_update_each_method
|
290
|
-
undef_method :orig_update_each_method
|
291
|
-
end
|
292
|
-
end
|
293
|
-
end
|
294
|
-
end
|
295
|
-
end
|
1
|
+
# ArrayKeys provide support for accessing array elements by keys. ArrayKeys are
|
2
|
+
# based on the arrayfields gem by Ara Howard, and can be used as substitutes
|
3
|
+
# for fetching records tuples as Ruby hashes.
|
4
|
+
#
|
5
|
+
# The main advantage offered by ArrayKeys over hashes is that the values are
|
6
|
+
# always ordered according to the column order in the query. Another purported
|
7
|
+
# advantage is that they reduce the memory footprint, but this has turned out
|
8
|
+
# to be a false claim.
|
9
|
+
module ArrayKeys
|
10
|
+
# The KeySet module contains methods that extend an array of keys to return
|
11
|
+
# a key's position in the key set.
|
12
|
+
module KeySet
|
13
|
+
# Returns the key's position in the key set. Provides indifferent access
|
14
|
+
# for symbols and strings.
|
15
|
+
def key_pos(key)
|
16
|
+
@key_indexes ||= inject({}) {|h, k| h[k.to_sym] = h.size; h}
|
17
|
+
@key_indexes[key] || @key_indexes[key.to_sym] || @key_indexes[key.to_s]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Adds a key to the key set.
|
21
|
+
def add_key(key)
|
22
|
+
self << key
|
23
|
+
@key_indexes[key] = @key_indexes.size
|
24
|
+
end
|
25
|
+
|
26
|
+
# Removes a key from the key set by its index.
|
27
|
+
def del_key(idx)
|
28
|
+
delete_at(idx)
|
29
|
+
@key_indexes = nil # reset key indexes
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# The KeyAccess provides a large part of the Hash API for arrays with keys.
|
34
|
+
module KeyAccess
|
35
|
+
# Returns a value referenced by an array index or a key.
|
36
|
+
def [](idx, *args)
|
37
|
+
if String === idx or Symbol === idx
|
38
|
+
(idx = @keys.key_pos(idx)) ? super(idx, *args) : nil
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Sets the value referenced by an array index or a key.
|
45
|
+
def []=(idx,*args)
|
46
|
+
if String === idx or Symbol === idx
|
47
|
+
idx = @keys.key_pos(idx) || @keys.add_key(idx.to_sym)
|
48
|
+
end
|
49
|
+
super(idx, *args)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Stores a value by index or key.
|
53
|
+
def store(k, v); self[k] = v; end
|
54
|
+
|
55
|
+
# Slices the array, and returns an array with its keys sliced accordingly.
|
56
|
+
def slice(*args)
|
57
|
+
s = super(*args)
|
58
|
+
s.keys = @keys.slice(*args)
|
59
|
+
s
|
60
|
+
end
|
61
|
+
|
62
|
+
# Converts the array into a hash.
|
63
|
+
def to_hash
|
64
|
+
h = {}
|
65
|
+
each_with_index {|v, i| h[@keys[i].to_sym] = v}
|
66
|
+
h
|
67
|
+
end
|
68
|
+
alias_method :to_h, :to_hash
|
69
|
+
|
70
|
+
# Iterates over each key-value pair in the array.
|
71
|
+
def each_pair
|
72
|
+
each_with_index {|v, i| yield @keys[i], v}
|
73
|
+
end
|
74
|
+
|
75
|
+
# Iterates over the array's associated keys.
|
76
|
+
def each_key(&block)
|
77
|
+
@keys.each(&block)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Iterates over the array's values.
|
81
|
+
def each_value(&block)
|
82
|
+
each(&block)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Deletes a value by its key.
|
86
|
+
def delete(key, *args)
|
87
|
+
if (idx = @keys.key_pos(key))
|
88
|
+
delete_at(idx)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Deletes a value by its index.
|
93
|
+
def delete_at(idx)
|
94
|
+
super(idx)
|
95
|
+
@keys = @keys.clone
|
96
|
+
@keys.del_key(idx)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns true if the array's key set contains the given key.
|
100
|
+
def include?(k)
|
101
|
+
@keys.include?(k) || @keys.include?(k.to_sym) || @keys.include?(k.to_s)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns true if the array's key set contains the given key.
|
105
|
+
def has_key?(k)
|
106
|
+
@keys.include?(k) || @keys.include?(k.to_sym) || @keys.include?(k.to_s)
|
107
|
+
end
|
108
|
+
alias_method :member?, :has_key?
|
109
|
+
alias_method :key?, :has_key?
|
110
|
+
|
111
|
+
# Returns true if the array contains the given value.
|
112
|
+
def has_value?(k); orig_include?(k); end
|
113
|
+
alias_method :value?, :has_value?
|
114
|
+
|
115
|
+
# Fetches a value by its key and optionally passes it through the given
|
116
|
+
# block:
|
117
|
+
#
|
118
|
+
# row.fetch(:name) {|v| v.to_sym}
|
119
|
+
#
|
120
|
+
# You can also give a default value
|
121
|
+
#
|
122
|
+
# row.fetch(:name, 'untitled')
|
123
|
+
#
|
124
|
+
def fetch(k, *args, &block)
|
125
|
+
if idx = @keys.key_pos(k)
|
126
|
+
v = at idx
|
127
|
+
else
|
128
|
+
!args.empty? ? (v = args.first) : (raise IndexError, "key not found")
|
129
|
+
end
|
130
|
+
block ? block[v] : v
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns self.
|
134
|
+
def values
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
# Creates a copy of self with the same key set.
|
139
|
+
def dup
|
140
|
+
copy = super
|
141
|
+
copy.keys = @keys
|
142
|
+
copy
|
143
|
+
end
|
144
|
+
|
145
|
+
# Creates a copy of self with a copy of the key set.
|
146
|
+
def clone
|
147
|
+
copy = super
|
148
|
+
copy.keys = @keys.clone
|
149
|
+
copy
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns an array merged from self and the given array.
|
153
|
+
def merge(values, &block)
|
154
|
+
clone.merge!(values, &block)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Merges the given array with self, optionally passing the values from self
|
158
|
+
# through the given block:
|
159
|
+
#
|
160
|
+
# row.merge!(new_values) {|k, old, new| (k == :name) ? old : new}
|
161
|
+
#
|
162
|
+
def merge!(values, &block)
|
163
|
+
values.each_pair do |k, v|
|
164
|
+
self[k] = (has_key?(k) && block) ? block[k, self[k], v] : v
|
165
|
+
end
|
166
|
+
self
|
167
|
+
end
|
168
|
+
alias_method :update, :merge!
|
169
|
+
alias_method :update!, :merge!
|
170
|
+
end
|
171
|
+
|
172
|
+
# The ArrayExtensions module provides extensions for the Array class.
|
173
|
+
module ArrayExtensions
|
174
|
+
attr_reader :keys
|
175
|
+
|
176
|
+
# Sets the key set for the array. Once a key set has been set for an array,
|
177
|
+
# it is extended with the KeyAccess API
|
178
|
+
def keys=(keys)
|
179
|
+
extend ArrayKeys::KeyAccess if keys
|
180
|
+
@keys = keys.frozen? ? keys.dup : keys
|
181
|
+
unless @keys.respond_to?(:key_pos)
|
182
|
+
@keys.extend(ArrayKeys::KeySet)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
alias_method :columns, :keys
|
187
|
+
alias_method :columns=, :keys=
|
188
|
+
end
|
189
|
+
|
190
|
+
# The DatasetExtensions module provides extensions that modify
|
191
|
+
# a dataset to return Array tuples instead of Hash tuples.
|
192
|
+
module DatasetExtensions
|
193
|
+
# Fetches a dataset's records, converting each tuple into an array with
|
194
|
+
# keys.
|
195
|
+
def array_tuples_each(opts = nil, &block)
|
196
|
+
fetch_rows(select_sql(opts)) {|h| block[Array.from_hash(h)]}
|
197
|
+
end
|
198
|
+
|
199
|
+
# Provides the corresponding behavior to Sequel::Dataset#update_each_method,
|
200
|
+
# using array tuples.
|
201
|
+
def array_tuples_update_each_method
|
202
|
+
# warning: ugly code generation ahead
|
203
|
+
if @row_proc && @transform
|
204
|
+
class << self
|
205
|
+
def each(opts = nil, &block)
|
206
|
+
if opts && opts[:naked]
|
207
|
+
fetch_rows(select_sql(opts)) {|r| block[transform_load(Array.from_hash(r))]}
|
208
|
+
else
|
209
|
+
fetch_rows(select_sql(opts)) {|r| block[@row_proc[transform_load(Array.from_hash(r))]]}
|
210
|
+
end
|
211
|
+
self
|
212
|
+
end
|
213
|
+
end
|
214
|
+
elsif @row_proc
|
215
|
+
class << self
|
216
|
+
def each(opts = nil, &block)
|
217
|
+
if opts && opts[:naked]
|
218
|
+
fetch_rows(select_sql(opts)) {|r| block[Array.from_hash(r)]}
|
219
|
+
else
|
220
|
+
fetch_rows(select_sql(opts)) {|r| block[@row_proc[Array.from_hash(r)]]}
|
221
|
+
end
|
222
|
+
self
|
223
|
+
end
|
224
|
+
end
|
225
|
+
elsif @transform
|
226
|
+
class << self
|
227
|
+
def each(opts = nil, &block)
|
228
|
+
fetch_rows(select_sql(opts)) {|r| block[transform_load(Array.from_hash(r))]}
|
229
|
+
self
|
230
|
+
end
|
231
|
+
end
|
232
|
+
else
|
233
|
+
class << self
|
234
|
+
def each(opts = nil, &block)
|
235
|
+
fetch_rows(select_sql(opts)) {|r| block[Array.from_hash(r)]}
|
236
|
+
self
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Array extensions.
|
245
|
+
class Array
|
246
|
+
alias_method :orig_include?, :include?
|
247
|
+
|
248
|
+
include ArrayKeys::ArrayExtensions
|
249
|
+
|
250
|
+
# Converts a hash into an array with keys.
|
251
|
+
def self.from_hash(h)
|
252
|
+
a = []; a.keys = []
|
253
|
+
a.merge!(h)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
module Sequel
|
258
|
+
# Modifies all dataset classes to fetch records as arrays with keys. By
|
259
|
+
# default records are fetched as hashes.
|
260
|
+
def self.use_array_tuples
|
261
|
+
Dataset.dataset_classes.each do |c|
|
262
|
+
c.class_eval do
|
263
|
+
if method_defined?(:array_tuples_fetch_rows)
|
264
|
+
alias_method :hash_tuples_fetch_rows, :fetch_rows
|
265
|
+
alias_method :fetch_rows, :array_tuples_fetch_rows
|
266
|
+
else
|
267
|
+
alias_method :orig_each, :each
|
268
|
+
alias_method :orig_update_each_method, :update_each_method
|
269
|
+
include ArrayKeys::DatasetExtensions
|
270
|
+
alias_method :each, :array_tuples_each
|
271
|
+
alias_method :update_each_method, :array_tuples_update_each_method
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Modifies all dataset classes to fetch records as hashes.
|
278
|
+
def self.use_hash_tuples
|
279
|
+
Dataset.dataset_classes.each do |c|
|
280
|
+
c.class_eval do
|
281
|
+
if method_defined?(:hash_tuples_fetch_rows)
|
282
|
+
alias_method :fetch_rows, :hash_tuples_fetch_rows
|
283
|
+
else
|
284
|
+
if method_defined?(:orig_each)
|
285
|
+
alias_method :each, :orig_each
|
286
|
+
undef_method :orig_each
|
287
|
+
end
|
288
|
+
if method_defined?(:orig_update_each_method)
|
289
|
+
alias_method :update_each_method, :orig_update_each_method
|
290
|
+
undef_method :orig_update_each_method
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
296
|
end
|