sequel_core 1.0.9.1 → 1.0.10

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 CHANGED
@@ -1,4 +1,14 @@
1
- === SVN
1
+ === 1.0.10 (2008-02-13)
2
+
3
+ * Fixed Datset#group_and_count to work inside a query block (#152).
4
+
5
+ * Changed datasets with transforms to automatically transform hash filters (#155).
6
+
7
+ * Changed Marshal stock transform to use Base64 encoding with backward-compatibility to support existing marshaled values (#154).
8
+
9
+ * Added support for inserting multiple records in a single statement using #multi_insert in MySQL adapter (#153).
10
+
11
+ * Added support for :slice option (same as :commit_every) in Dataset#multi_insert.
2
12
 
3
13
  * Changed Dataset#all to accept opts and iteration block.
4
14
 
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ include FileUtils
9
9
  # Configuration
10
10
  ##############################################################################
11
11
  NAME = "sequel_core"
12
- VERS = "1.0.9.1"
12
+ VERS = "1.0.10"
13
13
  CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
14
14
  RDOC_OPTS = [
15
15
  "--quiet",
@@ -357,6 +357,35 @@ module Sequel
357
357
  end
358
358
  self
359
359
  end
360
+
361
+ def multi_insert_sql(keys, array)
362
+ values = array.map {|r| "(#{literal(keys.map {|k| r[k]})})"}.join(COMMA_SEPARATOR)
363
+ columns = keys.map {|c| literal(c)}.join(COMMA_SEPARATOR)
364
+ "INSERT INTO #{@opts[:from]} (#{columns}) VALUES #{values}"
365
+ end
366
+
367
+ # Inserts multiple records into the associated table. This method can be
368
+ # to efficiently insert a large amounts of records into a table. Inserts
369
+ # are automatically wrapped in a transaction. If the :commit_every
370
+ # option is specified, the method will generate a separate transaction
371
+ # for each batch of records, e.g.:
372
+ #
373
+ # dataset.multi_insert(list, :commit_every => 1000)
374
+ def multi_insert(list, opts = {})
375
+ keys = list.first.keys
376
+
377
+ if every = (opts[:commit_every] || opts[:slice])
378
+ list.each_slice(every) do |s|
379
+ @db.transaction do
380
+ @db.execute(multi_insert_sql(keys, s))
381
+ end
382
+ end
383
+ else
384
+ @db.transaction do
385
+ @db.execute(multi_insert_sql(keys, list))
386
+ end
387
+ end
388
+ end
360
389
  end
361
390
  end
362
391
  end
@@ -221,12 +221,12 @@ module Sequel
221
221
  # Inserts multiple records into the associated table. This method can be
222
222
  # to efficiently insert a large amounts of records into a table. Inserts
223
223
  # are automatically wrapped in a transaction. If the :commit_every
224
- # option is specified, the method will generate a separate transaction
225
- # for each batch of records, e.g.:
224
+ # or :slice option is specified, the method will generate a separate
225
+ # transaction for each batch of records, e.g.:
226
226
  #
227
227
  # dataset.multi_insert(list, :commit_every => 1000)
228
228
  def multi_insert(list, opts = {})
229
- if every = opts[:commit_every]
229
+ if every = (opts[:commit_every] || opts[:slice])
230
230
  list.each_slice(every) do |s|
231
231
  @db.transaction do
232
232
  s.each {|r| @db.execute(insert_sql(r))}
@@ -246,9 +246,10 @@ module Sequel
246
246
  def insert(*args); raise Error, "#insert cannot be invoked inside a query block."; end
247
247
  def update(*args); raise Error, "#update cannot be invoked inside a query block."; end
248
248
  def delete(*args); raise Error, "#delete cannot be invoked inside a query block."; end
249
-
250
- def clone(opts)
249
+
250
+ def clone(opts = nil)
251
251
  @opts.merge!(opts)
252
+ self
252
253
  end
253
254
  end
254
255
 
@@ -265,8 +265,13 @@ module Sequel
265
265
  if cond === true || cond === false
266
266
  raise Error::InvalidFilter, "Invalid filter specified. Did you mean to supply a block?"
267
267
  end
268
+
269
+ if cond.is_a?(Hash)
270
+ cond = transform_save(cond) if @transform
271
+ filter = cond
272
+ end
268
273
  parenthesize = !(cond.is_a?(Hash) || cond.is_a?(Array))
269
- filter = cond.is_a?(Hash) && cond
274
+
270
275
  if !@opts[clause].nil? and @opts[clause].any?
271
276
  l = expression_list(@opts[clause])
272
277
  r = expression_list(block || cond, parenthesize)
@@ -1,6 +1,7 @@
1
1
  require 'time'
2
2
  require 'date'
3
3
  require 'yaml'
4
+ require 'base64'
4
5
 
5
6
  require File.join(File.dirname(__FILE__), 'dataset/sql')
6
7
  require File.join(File.dirname(__FILE__), 'dataset/sequelizer')
@@ -298,8 +299,15 @@ module Sequel
298
299
  end
299
300
 
300
301
  STOCK_TRANSFORMS = {
301
- :marshal => [proc {|v| Marshal.load(v)}, proc {|v| Marshal.dump(v)}],
302
- :yaml => [proc {|v| YAML.load v if v}, proc {|v| v.to_yaml}]
302
+ :marshal => [
303
+ # for backwards-compatibility we support also non-base64-encoded values.
304
+ proc {|v| Marshal.load(Base64.decode64(v)) rescue Marshal.load(v)},
305
+ proc {|v| Base64.encode64(Marshal.dump(v))}
306
+ ],
307
+ :yaml => [
308
+ proc {|v| YAML.load v if v},
309
+ proc {|v| v.to_yaml}
310
+ ]
303
311
  }
304
312
 
305
313
  # Sets a value transform which is used to convert values loaded and saved
@@ -264,7 +264,7 @@ end
264
264
  # end
265
265
  # end
266
266
  #
267
- context "Joiמed MySQL dataset" do
267
+ context "Joined MySQL dataset" do
268
268
  setup do
269
269
  @ds = MYSQL_DB[:nodes].join(:attributes, :node_id => :id)
270
270
  @ds2 = MYSQL_DB[:nodes]
@@ -440,4 +440,101 @@ context "A MySQL database" do
440
440
  MYSQL_DB[:posts].full_text_search(:title, '+ruby -rails', :boolean => true).sql.should ==
441
441
  "SELECT * FROM posts WHERE (MATCH (`title`) AGAINST ('+ruby -rails' IN BOOLEAN MODE))"
442
442
  end
443
+ end
444
+
445
+ class Sequel::MySQL::Database
446
+ alias_method :orig_execute, :execute
447
+ attr_accessor :sqls
448
+ def execute(sql, &block)
449
+ @sqls ||= []; @sqls << sql
450
+ orig_execute(sql, &block)
451
+ end
452
+
453
+ def transaction
454
+ @pool.hold do |conn|
455
+ @transactions ||= []
456
+ if @transactions.include? Thread.current
457
+ return yield(conn)
458
+ end
459
+ @sqls ||= []; @sqls << SQL_BEGIN
460
+ conn.query(SQL_BEGIN)
461
+ begin
462
+ @transactions << Thread.current
463
+ result = yield(conn)
464
+ @sqls ||= []; @sqls << SQL_COMMIT
465
+ conn.query(SQL_COMMIT)
466
+ result
467
+ rescue => e
468
+ @sqls ||= []; @sqls << SQL_ROLLBACK
469
+ conn.query(SQL_ROLLBACK)
470
+ raise e unless Sequel::Error::Rollback === e
471
+ ensure
472
+ @transactions.delete(Thread.current)
473
+ end
474
+ end
475
+ end
476
+ end
477
+
478
+ context "MySQL::Dataset#multi_insert" do
479
+ setup do
480
+ @d = MYSQL_DB[:items]
481
+ @d.delete # remove all records
482
+ MYSQL_DB.sqls.clear
483
+ end
484
+
485
+ specify "should insert multiple records in a single statement" do
486
+ @d.multi_insert([{:name => 'abc'}, {:name => 'def'}])
487
+
488
+ MYSQL_DB.sqls.should == [
489
+ 'BEGIN',
490
+ "INSERT INTO items (`name`) VALUES ('abc'), ('def')",
491
+ 'COMMIT'
492
+ ]
493
+
494
+ @d.all.should == [
495
+ {:name => 'abc', :value => nil}, {:name => 'def', :value => nil}
496
+ ]
497
+ end
498
+
499
+ specify "should split the list of records into batches if :commit_every option is given" do
500
+ @d.multi_insert([{:value => 1}, {:value => 2}, {:value => 3}, {:value => 4}],
501
+ :commit_every => 2)
502
+
503
+ MYSQL_DB.sqls.should == [
504
+ 'BEGIN',
505
+ "INSERT INTO items (`value`) VALUES (1), (2)",
506
+ 'COMMIT',
507
+ 'BEGIN',
508
+ "INSERT INTO items (`value`) VALUES (3), (4)",
509
+ 'COMMIT'
510
+ ]
511
+
512
+ @d.all.should == [
513
+ {:name => nil, :value => 1},
514
+ {:name => nil, :value => 2},
515
+ {:name => nil, :value => 3},
516
+ {:name => nil, :value => 4}
517
+ ]
518
+ end
519
+
520
+ specify "should split the list of records into batches if :slice option is given" do
521
+ @d.multi_insert([{:value => 1}, {:value => 2}, {:value => 3}, {:value => 4}],
522
+ :slice => 2)
523
+
524
+ MYSQL_DB.sqls.should == [
525
+ 'BEGIN',
526
+ "INSERT INTO items (`value`) VALUES (1), (2)",
527
+ 'COMMIT',
528
+ 'BEGIN',
529
+ "INSERT INTO items (`value`) VALUES (3), (4)",
530
+ 'COMMIT'
531
+ ]
532
+
533
+ @d.all.should == [
534
+ {:name => nil, :value => 1},
535
+ {:name => nil, :value => 2},
536
+ {:name => nil, :value => 3},
537
+ {:name => nil, :value => 4}
538
+ ]
539
+ end
443
540
  end
data/spec/dataset_spec.rb CHANGED
@@ -1032,11 +1032,18 @@ context "Dataset#group_and_count" do
1032
1032
  end
1033
1033
 
1034
1034
  specify "should format SQL properly" do
1035
- @ds.group_and_count(:name).sql.should == "SELECT name, count(*) AS count FROM test GROUP BY name ORDER BY count"
1035
+ @ds.group_and_count(:name).sql.should ==
1036
+ "SELECT name, count(*) AS count FROM test GROUP BY name ORDER BY count"
1036
1037
  end
1037
1038
 
1038
1039
  specify "should accept multiple columns for grouping" do
1039
- @ds.group_and_count(:a, :b).sql.should == "SELECT a, b, count(*) AS count FROM test GROUP BY a, b ORDER BY count"
1040
+ @ds.group_and_count(:a, :b).sql.should ==
1041
+ "SELECT a, b, count(*) AS count FROM test GROUP BY a, b ORDER BY count"
1042
+ end
1043
+
1044
+ specify "should work within query block" do
1045
+ @ds.query{group_and_count(:a, :b)}.sql.should ==
1046
+ "SELECT a, b, count(*) AS count FROM test GROUP BY a, b ORDER BY count"
1040
1047
  end
1041
1048
  end
1042
1049
 
@@ -2053,7 +2060,7 @@ context "Dataset#multi_insert" do
2053
2060
  ]
2054
2061
  end
2055
2062
 
2056
- specify "should accept the commit_every option for committing every x records" do
2063
+ specify "should accept the :commit_every option for committing every x records" do
2057
2064
  @ds.multi_insert(@list, :commit_every => 2)
2058
2065
  @db.sqls.should == [
2059
2066
  'BEGIN',
@@ -2065,6 +2072,19 @@ context "Dataset#multi_insert" do
2065
2072
  'COMMIT'
2066
2073
  ]
2067
2074
  end
2075
+
2076
+ specify "should accept the :slice option for committing every x records" do
2077
+ @ds.multi_insert(@list, :slice => 2)
2078
+ @db.sqls.should == [
2079
+ 'BEGIN',
2080
+ "INSERT INTO items (name) VALUES ('abc')",
2081
+ "INSERT INTO items (name) VALUES ('def')",
2082
+ 'COMMIT',
2083
+ 'BEGIN',
2084
+ "INSERT INTO items (name) VALUES ('ghi')",
2085
+ 'COMMIT'
2086
+ ]
2087
+ end
2068
2088
  end
2069
2089
 
2070
2090
  context "Dataset#query" do
@@ -2310,39 +2330,59 @@ context "Dataset#transform" do
2310
2330
  f.should == {:x => "wow", :y => 'hello'}
2311
2331
  end
2312
2332
 
2313
- specify "should support stock Marshal transformation" do
2333
+ specify "should support stock Marshal transformation with Base64 encoding" do
2314
2334
  @ds.transform(:x => :marshal)
2315
2335
 
2316
- @ds.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
2336
+ @ds.raw = {:x => Base64.encode64(Marshal.dump([1, 2, 3])), :y => 'hello'}
2317
2337
  @ds.first.should == {:x => [1, 2, 3], :y => 'hello'}
2318
2338
 
2319
2339
  @ds.insert(:x => :toast)
2320
- @ds.sql.should == "INSERT INTO items (x) VALUES ('#{Marshal.dump(:toast)}')"
2340
+ @ds.sql.should == "INSERT INTO items (x) VALUES ('#{Base64.encode64(Marshal.dump(:toast))}')"
2321
2341
  @ds.insert(:y => 'butter')
2322
2342
  @ds.sql.should == "INSERT INTO items (y) VALUES ('butter')"
2323
2343
  @ds.update(:x => ['dream'])
2324
- @ds.sql.should == "UPDATE items SET x = '#{Marshal.dump(['dream'])}'"
2344
+ @ds.sql.should == "UPDATE items SET x = '#{Base64.encode64(Marshal.dump(['dream']))}'"
2325
2345
 
2326
2346
  @ds2 = @ds.filter(:a => 1)
2327
- @ds2.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
2347
+ @ds2.raw = {:x => Base64.encode64(Marshal.dump([1, 2, 3])), :y => 'hello'}
2328
2348
  @ds2.first.should == {:x => [1, 2, 3], :y => 'hello'}
2329
2349
  @ds2.insert(:x => :toast)
2330
- @ds2.sql.should == "INSERT INTO items (x) VALUES ('#{Marshal.dump(:toast)}')"
2350
+ @ds2.sql.should == "INSERT INTO items (x) VALUES ('#{Base64.encode64(Marshal.dump(:toast))}')"
2331
2351
 
2332
2352
  @ds.set_row_proc {|r| r[:z] = r[:x] * 2; r}
2333
- @ds.raw = {:x => Marshal.dump("wow"), :y => 'hello'}
2353
+ @ds.raw = {:x => Base64.encode64(Marshal.dump("wow")), :y => 'hello'}
2334
2354
  @ds.first.should == {:x => "wow", :y => 'hello', :z => "wowwow"}
2335
2355
  f = nil
2336
- @ds.raw = {:x => Marshal.dump("wow"), :y => 'hello'}
2356
+ @ds.raw = {:x => Base64.encode64(Marshal.dump("wow")), :y => 'hello'}
2337
2357
  @ds.each(:naked => true) {|r| f = r}
2338
2358
  f.should == {:x => "wow", :y => 'hello'}
2339
2359
  end
2340
2360
 
2361
+ specify "should support loading of Marshalled values without Base64 encoding" do
2362
+ @ds.transform(:x => :marshal)
2363
+
2364
+ @ds.raw = {:x => Marshal.dump([1,2,3]), :y => nil}
2365
+ @ds.first.should == {:x => [1,2,3], :y => nil}
2366
+ end
2367
+
2341
2368
  specify "should return self" do
2342
2369
  @ds.transform(:x => :marshal).should be(@ds)
2343
2370
  end
2344
2371
  end
2345
2372
 
2373
+ context "A dataset with a transform" do
2374
+ setup do
2375
+ @ds = Sequel::Dataset.new(nil).from(:items)
2376
+ @ds.transform(:x => :marshal)
2377
+ end
2378
+
2379
+ specify "should automatically transform hash filters" do
2380
+ @ds.filter(:y => 2).sql.should == 'SELECT * FROM items WHERE (y = 2)'
2381
+
2382
+ @ds.filter(:x => 2).sql.should == "SELECT * FROM items WHERE (x = '#{Base64.encode64(Marshal.dump(2))}')"
2383
+ end
2384
+ end
2385
+
2346
2386
  context "Dataset#to_csv" do
2347
2387
  setup do
2348
2388
  @c = Class.new(Sequel::Dataset) do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.9.1
4
+ version: 1.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-02-11 00:00:00 +02:00
12
+ date: 2008-02-14 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency