sequel_core 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.
Files changed (57) hide show
  1. data/CHANGELOG +1003 -0
  2. data/COPYING +18 -0
  3. data/README +81 -0
  4. data/Rakefile +176 -0
  5. data/bin/sequel +41 -0
  6. data/lib/sequel_core.rb +59 -0
  7. data/lib/sequel_core/adapters/adapter_skeleton.rb +68 -0
  8. data/lib/sequel_core/adapters/ado.rb +100 -0
  9. data/lib/sequel_core/adapters/db2.rb +158 -0
  10. data/lib/sequel_core/adapters/dbi.rb +126 -0
  11. data/lib/sequel_core/adapters/informix.rb +87 -0
  12. data/lib/sequel_core/adapters/jdbc.rb +108 -0
  13. data/lib/sequel_core/adapters/mysql.rb +269 -0
  14. data/lib/sequel_core/adapters/odbc.rb +145 -0
  15. data/lib/sequel_core/adapters/odbc_mssql.rb +93 -0
  16. data/lib/sequel_core/adapters/openbase.rb +90 -0
  17. data/lib/sequel_core/adapters/oracle.rb +99 -0
  18. data/lib/sequel_core/adapters/postgres.rb +519 -0
  19. data/lib/sequel_core/adapters/sqlite.rb +192 -0
  20. data/lib/sequel_core/array_keys.rb +296 -0
  21. data/lib/sequel_core/connection_pool.rb +152 -0
  22. data/lib/sequel_core/core_ext.rb +59 -0
  23. data/lib/sequel_core/core_sql.rb +191 -0
  24. data/lib/sequel_core/database.rb +433 -0
  25. data/lib/sequel_core/dataset.rb +409 -0
  26. data/lib/sequel_core/dataset/convenience.rb +321 -0
  27. data/lib/sequel_core/dataset/sequelizer.rb +354 -0
  28. data/lib/sequel_core/dataset/sql.rb +586 -0
  29. data/lib/sequel_core/exceptions.rb +45 -0
  30. data/lib/sequel_core/migration.rb +191 -0
  31. data/lib/sequel_core/model.rb +8 -0
  32. data/lib/sequel_core/pretty_table.rb +73 -0
  33. data/lib/sequel_core/schema.rb +8 -0
  34. data/lib/sequel_core/schema/schema_generator.rb +131 -0
  35. data/lib/sequel_core/schema/schema_sql.rb +131 -0
  36. data/lib/sequel_core/worker.rb +58 -0
  37. data/spec/adapters/informix_spec.rb +139 -0
  38. data/spec/adapters/mysql_spec.rb +330 -0
  39. data/spec/adapters/oracle_spec.rb +130 -0
  40. data/spec/adapters/postgres_spec.rb +189 -0
  41. data/spec/adapters/sqlite_spec.rb +345 -0
  42. data/spec/array_keys_spec.rb +679 -0
  43. data/spec/connection_pool_spec.rb +356 -0
  44. data/spec/core_ext_spec.rb +67 -0
  45. data/spec/core_sql_spec.rb +301 -0
  46. data/spec/database_spec.rb +812 -0
  47. data/spec/dataset_spec.rb +2381 -0
  48. data/spec/migration_spec.rb +261 -0
  49. data/spec/pretty_table_spec.rb +66 -0
  50. data/spec/rcov.opts +4 -0
  51. data/spec/schema_generator_spec.rb +86 -0
  52. data/spec/schema_spec.rb +230 -0
  53. data/spec/sequelizer_spec.rb +448 -0
  54. data/spec/spec.opts +5 -0
  55. data/spec/spec_helper.rb +44 -0
  56. data/spec/worker_spec.rb +96 -0
  57. metadata +162 -0
@@ -0,0 +1,192 @@
1
+ require 'sqlite3'
2
+
3
+ module Sequel
4
+ module SQLite
5
+ class Database < Sequel::Database
6
+ set_adapter_scheme :sqlite
7
+
8
+ def serial_primary_key_options
9
+ {:primary_key => true, :type => :integer, :auto_increment => true}
10
+ end
11
+
12
+ def connect
13
+ if @opts[:database].nil? || @opts[:database].empty?
14
+ @opts[:database] = ':memory:'
15
+ end
16
+ db = ::SQLite3::Database.new(@opts[:database])
17
+ db.type_translation = true
18
+ # fix for timestamp translation
19
+ db.translator.add_translator("timestamp") do |t, v|
20
+ v =~ /^\d+$/ ? Time.at(v.to_i) : Time.parse(v)
21
+ end
22
+ db
23
+ end
24
+
25
+ def disconnect
26
+ @pool.disconnect {|c| c.close}
27
+ end
28
+
29
+ def dataset(opts = nil)
30
+ SQLite::Dataset.new(self, opts)
31
+ end
32
+
33
+ TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'"
34
+
35
+ def tables
36
+ self[:sqlite_master].filter(TABLES_FILTER).map {|r| r[:name].to_sym}
37
+ end
38
+
39
+ def execute(sql)
40
+ @logger.info(sql) if @logger
41
+ @pool.hold {|conn| conn.execute_batch(sql); conn.changes}
42
+ end
43
+
44
+ def execute_insert(sql)
45
+ @logger.info(sql) if @logger
46
+ @pool.hold {|conn| conn.execute(sql); conn.last_insert_row_id}
47
+ end
48
+
49
+ def single_value(sql)
50
+ @logger.info(sql) if @logger
51
+ @pool.hold {|conn| conn.get_first_value(sql)}
52
+ end
53
+
54
+ def execute_select(sql, &block)
55
+ @logger.info(sql) if @logger
56
+ @pool.hold {|conn| conn.query(sql, &block)}
57
+ end
58
+
59
+ def pragma_get(name)
60
+ single_value("PRAGMA #{name}")
61
+ end
62
+
63
+ def pragma_set(name, value)
64
+ execute("PRAGMA #{name} = #{value}")
65
+ end
66
+
67
+ AUTO_VACUUM = {'0' => :none, '1' => :full, '2' => :incremental}.freeze
68
+
69
+ def auto_vacuum
70
+ AUTO_VACUUM[pragma_get(:auto_vacuum)]
71
+ end
72
+
73
+ def auto_vacuum=(value)
74
+ value = AUTO_VACUUM.index(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
75
+ pragma_set(:auto_vacuum, value)
76
+ end
77
+
78
+ SYNCHRONOUS = {'0' => :off, '1' => :normal, '2' => :full}.freeze
79
+
80
+ def synchronous
81
+ SYNCHRONOUS[pragma_get(:synchronous)]
82
+ end
83
+
84
+ def synchronous=(value)
85
+ value = SYNCHRONOUS.index(value) || (raise Error, "Invalid value for synchronous option. Please specify one of :off, :normal, :full.")
86
+ pragma_set(:synchronous, value)
87
+ end
88
+
89
+ TEMP_STORE = {'0' => :default, '1' => :file, '2' => :memory}.freeze
90
+
91
+ def temp_store
92
+ TEMP_STORE[pragma_get(:temp_store)]
93
+ end
94
+
95
+ def temp_store=(value)
96
+ value = TEMP_STORE.index(value) || (raise Error, "Invalid value for temp_store option. Please specify one of :default, :file, :memory.")
97
+ pragma_set(:temp_store, value)
98
+ end
99
+
100
+ def alter_table_sql(table, op)
101
+ case op[:op]
102
+ when :add_column
103
+ "ALTER TABLE #{table} ADD #{column_definition_sql(op)}"
104
+ else
105
+ raise Error, "Unsupported ALTER TABLE operation"
106
+ end
107
+ end
108
+
109
+ def transaction(&block)
110
+ @pool.hold do |conn|
111
+ if conn.transaction_active?
112
+ return yield(conn)
113
+ end
114
+ begin
115
+ result = nil
116
+ conn.transaction {result = yield(conn)}
117
+ result
118
+ rescue => e
119
+ raise e unless Error::Rollback === e
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ class Dataset < Sequel::Dataset
126
+ def literal(v)
127
+ case v
128
+ when Time
129
+ literal(v.iso8601)
130
+ else
131
+ super
132
+ end
133
+ end
134
+
135
+ def insert_sql(*values)
136
+ if (values.size == 1) && values.first.is_a?(Sequel::Dataset)
137
+ "INSERT INTO #{@opts[:from]} #{values.first.sql};"
138
+ else
139
+ super(*values)
140
+ end
141
+ end
142
+
143
+ def fetch_rows(sql, &block)
144
+ @db.execute_select(sql) do |result|
145
+ @columns = result.columns.map {|c| c.to_sym}
146
+ column_count = @columns.size
147
+ result.each do |values|
148
+ row = {}
149
+ column_count.times {|i| row[@columns[i]] = values[i]}
150
+ block.call(row)
151
+ end
152
+ end
153
+ end
154
+
155
+ def array_tuples_fetch_rows(sql, &block)
156
+ @db.execute_select(sql) do |result|
157
+ @columns = result.columns.map {|c| c.to_sym}
158
+ result.each {|r| r.keys = @columns; block[r]}
159
+ end
160
+ end
161
+
162
+ def insert(*values)
163
+ @db.execute_insert insert_sql(*values)
164
+ end
165
+
166
+ def update(*args, &block)
167
+ @db.execute update_sql(*args, &block)
168
+ end
169
+
170
+ def delete(opts = nil)
171
+ # check if no filter is specified
172
+ unless (opts && opts[:where]) || @opts[:where]
173
+ @db.transaction do
174
+ unfiltered_count = count
175
+ @db.execute delete_sql(opts)
176
+ unfiltered_count
177
+ end
178
+ else
179
+ @db.execute delete_sql(opts)
180
+ end
181
+ end
182
+
183
+ EXPLAIN = 'EXPLAIN %s'.freeze
184
+
185
+ def explain
186
+ res = []
187
+ @db.result_set(EXPLAIN % select_sql(opts), nil) {|r| res << r}
188
+ res
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +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
296
+ end