sequel_core 1.0.9.1 → 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
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