sequel 3.41.0 → 3.42.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,31 @@
1
+ === 3.42.0 (2012-12-03)
2
+
3
+ * If an exception occurs while committing a transaction, attempt to rollback (jeremyevans)
4
+
5
+ * Support setting default string column sizes on a per-Database basis via default_string_column_size (jeremyevans)
6
+
7
+ * Reset Model.instance_dataset when extending the model's dataset (jeremyevans)
8
+
9
+ * Make the force_encoding plugin work with frozen strings (jeremyevans)
10
+
11
+ * Add Database#do on PostgreSQL for using the DO anonymous code block execution statement (jeremyevans)
12
+
13
+ * Remove Model.dataset_methods (jeremyevans)
14
+
15
+ * Allow subset to be called inside a dataset_module block (jeremyevans)
16
+
17
+ * Make Dataset#avg, #interval, #min, #max, #range, and #sum accept virtual row blocks (jeremyevans)
18
+
19
+ * Make Dataset#count use a subselect when the dataset has an offset without a limit (jeremyevans) (#587)
20
+
21
+ * Dump deferrable status of unique indexes on PostgreSQL (radford) (#583)
22
+
23
+ * Extend deferrable constraint support to all types of constraints, not just foreign keys (radford, jeremyevans) (#583)
24
+
25
+ * Support Database#copy_table and #copy_into on jdbc/postgres (bdon) (#580)
26
+
27
+ * Make Dataset#update not use a limit (TOP) on Microsoft SQL Server 2000 (jeremyevans) (#578)
28
+
1
29
  === 3.41.0 (2012-11-01)
2
30
 
3
31
  * Add bin/sequel usage guide (jeremyevans)
data/README.rdoc CHANGED
@@ -395,10 +395,10 @@ and not raise an exception outside the block, you can raise the
395
395
  Sequel makes it easy to join tables:
396
396
 
397
397
  order_items = DB[:items].join(:order_items, :item_id => :id).
398
- where(:order_items__order_id => 1234)
398
+ where(:order_id => 1234)
399
399
  # SELECT * FROM items INNER JOIN order_items
400
400
  # ON order_items.item_id = items.id
401
- # WHERE order_items.order_id = 1234
401
+ # WHERE order_id = 1234
402
402
 
403
403
  The important thing to note here is that item_id is automatically qualified with
404
404
  the table being joined, and id is automatically qualified with the last table
@@ -437,9 +437,10 @@ Ruby strings are generally treated as SQL strings:
437
437
  items.where(:x => 'x')
438
438
  # SELECT * FROM items WHERE (x = 'x')
439
439
 
440
- === Qualifying column names
440
+ === Qualifying identifiers (column/table names)
441
441
 
442
- Column references can be qualified by using the double underscore special notation <tt>:table__column</tt>:
442
+ An identifier in SQL is a name that represents a column, table, or schema.
443
+ Identifiers can be qualified by using the double underscore special notation <tt>:table__column</tt>:
443
444
 
444
445
  items.literal(:items__price)
445
446
  # items.price
@@ -449,9 +450,15 @@ Another way to qualify columns is to use the <tt>Sequel.qualify</tt> method:
449
450
  items.literal(Sequel.qualify(:items, :price))
450
451
  # items.price
451
452
 
452
- === Column aliases
453
+ While it is more common to qualify column identifiers with table identifiers, you can also qualify table identifiers with schema identifiers
454
+ to select from a qualified table:
455
+
456
+ posts = DB[:some_schema__posts]
457
+ # SELECT * FROM some_schema.posts
458
+
459
+ === Identifier aliases
453
460
 
454
- You can also alias columns by using the triple undersecore special notation <tt>:column___alias</tt> or <tt>:table__column___alias</tt>:
461
+ You can also alias identifiers by using the triple undersecore special notation <tt>:column___alias</tt> or <tt>:table__column___alias</tt>:
455
462
 
456
463
  items.literal(:price___p)
457
464
  # price AS p
@@ -463,6 +470,11 @@ Another way to alias columns is to use the <tt>Sequel.as</tt> method:
463
470
  items.literal(Sequel.as(:price, :p))
464
471
  # price AS p
465
472
 
473
+ You can use the <tt>Sequel.as</tt> method to alias arbitrary expressions, not just identifiers:
474
+
475
+ items.literal(Sequel.as(DB[:posts].select{max(id)}, :p))
476
+ # (SELECT max(id) FROM posts) AS p
477
+
466
478
  == Sequel Models
467
479
 
468
480
  A model class wraps a dataset, and an instance of that class wraps a single record in the dataset.
data/Rakefile CHANGED
@@ -32,6 +32,18 @@ task :release=>[:package] do
32
32
  sh %{gem push ./#{NAME}-#{VERS.call}.gem}
33
33
  end
34
34
 
35
+ ### Website
36
+
37
+ desc "Make local version of website"
38
+ task :website do
39
+ sh %{#{FileUtils::RUBY} www/make_www.rb}
40
+ end
41
+
42
+ desc "Update Non-RDoc section of sequel.rubyforge.org"
43
+ task :website_rf_base=>[:website] do
44
+ sh %{rsync -rt www/public/*.html rubyforge.org:/var/www/gforge-projects/sequel/}
45
+ end
46
+
35
47
  ### RDoc
36
48
 
37
49
  RDOC_DEFAULT_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', 'Sequel: The Database Toolkit for Ruby']
@@ -41,54 +53,47 @@ rdoc_task_class = begin
41
53
  RDOC_DEFAULT_OPTS.concat(['-f', 'hanna'])
42
54
  RDoc::Task
43
55
  rescue LoadError
44
- require "rake/rdoctask"
45
- Rake::RDocTask
56
+ begin
57
+ require "rake/rdoctask"
58
+ Rake::RDocTask
59
+ rescue LoadError
60
+ end
46
61
  end
47
62
 
48
- RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
63
+ if rdoc_task_class
64
+ RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
49
65
 
50
- rdoc_task_class.new do |rdoc|
51
- rdoc.rdoc_dir = "rdoc"
52
- rdoc.options += RDOC_OPTS
53
- rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb doc/*.rdoc doc/release_notes/*.txt"
54
- end
66
+ rdoc_task_class.new do |rdoc|
67
+ rdoc.rdoc_dir = "rdoc"
68
+ rdoc.options += RDOC_OPTS
69
+ rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb doc/*.rdoc doc/release_notes/*.txt"
70
+ end if rdoc_task_class
55
71
 
56
- ### Website
72
+ desc "Make rdoc for website"
73
+ task :website_rdoc=>[:website_rdoc_main, :website_rdoc_adapters, :website_rdoc_plugins]
57
74
 
58
- desc "Make local version of website"
59
- task :website do
60
- sh %{#{FileUtils::RUBY} www/make_www.rb}
61
- end
62
-
63
- desc "Make rdoc for website"
64
- task :website_rdoc=>[:website_rdoc_main, :website_rdoc_adapters, :website_rdoc_plugins]
65
-
66
- rdoc_task_class.new(:website_rdoc_main) do |rdoc|
67
- rdoc.rdoc_dir = "www/public/rdoc"
68
- rdoc.options += RDOC_OPTS
69
- rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/*.rb lib/sequel/*.rb lib/sequel/{connection_pool,dataset,database,model}/*.rb doc/*.rdoc doc/release_notes/*.txt lib/sequel/extensions/migration.rb"
70
- end
71
-
72
- rdoc_task_class.new(:website_rdoc_adapters) do |rdoc|
73
- rdoc.rdoc_dir = "www/public/rdoc-adapters"
74
- rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel'
75
- rdoc.rdoc_files.add %w"lib/sequel/adapters/**/*.rb"
76
- end
75
+ rdoc_task_class.new(:website_rdoc_main) do |rdoc|
76
+ rdoc.rdoc_dir = "www/public/rdoc"
77
+ rdoc.options += RDOC_OPTS
78
+ rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/*.rb lib/sequel/*.rb lib/sequel/{connection_pool,dataset,database,model}/*.rb doc/*.rdoc doc/release_notes/*.txt lib/sequel/extensions/migration.rb"
79
+ end
77
80
 
78
- rdoc_task_class.new(:website_rdoc_plugins) do |rdoc|
79
- rdoc.rdoc_dir = "www/public/rdoc-plugins"
80
- rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel'
81
- rdoc.rdoc_files.add %w"lib/sequel/{extensions,plugins}/**/*.rb"
82
- end
81
+ rdoc_task_class.new(:website_rdoc_adapters) do |rdoc|
82
+ rdoc.rdoc_dir = "www/public/rdoc-adapters"
83
+ rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel'
84
+ rdoc.rdoc_files.add %w"lib/sequel/adapters/**/*.rb"
85
+ end
83
86
 
84
- desc "Update Non-RDoc section of sequel.rubyforge.org"
85
- task :website_rf_base=>[:website] do
86
- sh %{rsync -rt www/public/*.html rubyforge.org:/var/www/gforge-projects/sequel/}
87
- end
87
+ rdoc_task_class.new(:website_rdoc_plugins) do |rdoc|
88
+ rdoc.rdoc_dir = "www/public/rdoc-plugins"
89
+ rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel'
90
+ rdoc.rdoc_files.add %w"lib/sequel/{extensions,plugins}/**/*.rb"
91
+ end
88
92
 
89
- desc "Update sequel.rubyforge.org"
90
- task :website_rf=>[:website, :website_rdoc] do
91
- sh %{rsync -rvt www/public/* rubyforge.org:/var/www/gforge-projects/sequel/}
93
+ desc "Update sequel.rubyforge.org"
94
+ task :website_rf=>[:website, :website_rdoc] do
95
+ sh %{rsync -rvt www/public/* rubyforge.org:/var/www/gforge-projects/sequel/}
96
+ end
92
97
  end
93
98
 
94
99
  ### Specs
@@ -0,0 +1,74 @@
1
+ = New Features
2
+
3
+ * Dataset#avg, #interval, #min, #max, #range, and #sum now
4
+ accept virtual row blocks, allowing you to more easily get
5
+ aggregate values of expressions based on the table:
6
+
7
+ DB[:table].sum{some_function(column1, column2)} # => 134
8
+ # SELECT sum(some_function(column1, column2)) FROM table
9
+
10
+ * Database#do has been added on PostgreSQL for using the DO
11
+ anonymous code block execution statement.
12
+
13
+ * Model.dataset_module now uses a Module subclass, which allows
14
+ you to call subset inside a dataset_module block, making
15
+ it easier to consolidate dataset method code:
16
+
17
+ class Album < Sequel::Model
18
+ dataset_module do
19
+ subset(:gold){copies_sold > 500000}
20
+ end
21
+ end
22
+
23
+ * Database#copy_table and #copy_into are now supported on
24
+ jdbc/postgres.
25
+
26
+ * Sequel now supports deferred constraints on constraint types other
27
+ than foreign keys. The only databases that appear to implement
28
+ this are Oracle and PostgreSQL.
29
+
30
+ * Sequel now supports INITIALLY IMMEDIATE deferred constraints via
31
+ the :deferrable=>:immediate constraint/column option.
32
+
33
+ * Sequel now supports setting the default size of string columns,
34
+ via the default_string_column_size option or accessor. In some
35
+ cases, Sequel's default string column size of 255 is too large
36
+ (e.g. MySQL with utf8mb4 character set), and this allows you to
37
+ change it.
38
+
39
+ = Other Improvements
40
+
41
+ * Dataset#count and other methods now use a subselect in the case
42
+ where the dataset has an offset but no limit.
43
+
44
+ * If an error occurs while attempting to commit a transaction, Sequel
45
+ now attempts to rollback the transaction. Some databases do this
46
+ automatically, but not all. Among other things, this fixes issues
47
+ with deferred foreign key constraint violations on SQLite.
48
+
49
+ * When extending a model's dataset, the model's instance_dataset is
50
+ reset, insuring that it will also be extended with the module.
51
+
52
+ * When passing an invalid argument to Dataset#filter, the exception
53
+ message now includes the argument.
54
+
55
+ * The force_encoding plugin now works with frozen string values.
56
+
57
+ * Public methods added to a model dataset_module now have model
58
+ class methods created for them even if the method was added outside
59
+ of a dataset_module block.
60
+
61
+ * On PostgreSQL, Database#indexes now includes a :deferrable entry
62
+ for each index hash, which will be true for unique indexes where
63
+ the underlying constraint is deferrable.
64
+
65
+ * On Microsoft SQL Server 2000, Dataset#update no longer includes a
66
+ limit (TOP), allowing it to work correctly.
67
+
68
+ = Backwards Compatibility
69
+
70
+ * Model.dataset_methods has been removed. This was used to store
71
+ blocks for methods created via def_dataset_method and subset.
72
+ The internals have been changed so that a dataset_module is
73
+ always used in these cases, therefore there was no longer a reason
74
+ for this method.
@@ -17,7 +17,62 @@ module Sequel
17
17
  def self.extended(db)
18
18
  db.send(:initialize_postgres_adapter)
19
19
  end
20
+
21
+ # See Sequel::Postgres::Adapter#copy_into
22
+ def copy_into(table, opts={})
23
+ data = opts[:data]
24
+ data = Array(data) if data.is_a?(String)
25
+
26
+ if block_given? && data
27
+ raise Error, "Cannot provide both a :data option and a block to copy_into"
28
+ elsif !block_given? && !data
29
+ raise Error, "Must provide either a :data option or a block to copy_into"
30
+ end
31
+
32
+ transaction(opts) do |conn|
33
+ begin
34
+ copy_manager = org.postgresql.copy.CopyManager.new(conn)
35
+ copier = copy_manager.copy_in(copy_into_sql(table, opts))
36
+ if block_given?
37
+ while buf = yield
38
+ copier.writeToCopy(buf.to_java_bytes, 0, buf.length)
39
+ end
40
+ else
41
+ data.each { |d| copier.writeToCopy(d.to_java_bytes, 0, d.length) }
42
+ end
43
+ rescue Exception => e
44
+ copier.endCopy
45
+ raise
46
+ ensure
47
+ copier.endCopy unless e
48
+ end
49
+ end
50
+ end
20
51
 
52
+ # See Sequel::Postgres::Adapter#copy_table
53
+ def copy_table(table, opts={})
54
+ synchronize(opts[:server]) do |conn|
55
+ copy_manager = org.postgresql.copy.CopyManager.new(conn)
56
+ copier = copy_manager.copy_out(copy_table_sql(table, opts))
57
+ begin
58
+ if block_given?
59
+ while buf = copier.readFromCopy
60
+ yield(String.from_java_bytes(buf))
61
+ end
62
+ nil
63
+ else
64
+ b = ''
65
+ while buf = copier.readFromCopy
66
+ b << String.from_java_bytes(buf)
67
+ end
68
+ b
69
+ end
70
+ ensure
71
+ raise DatabaseDisconnectError, "disconnecting as a partial COPY may leave the connection in an unusable state" if buf
72
+ end
73
+ end
74
+ end
75
+
21
76
  private
22
77
 
23
78
  # Use setNull for nil arguments as the default behavior of setString
@@ -268,24 +268,8 @@ module Sequel
268
268
  # per row. If a block is not provided, a single string is returned with all
269
269
  # of the data.
270
270
  def copy_table(table, opts={})
271
- sql = if table.is_a?(String)
272
- sql = table
273
- else
274
- if opts[:options] || opts[:format]
275
- options = " ("
276
- options << "FORMAT #{opts[:format]}" if opts[:format]
277
- options << "#{', ' if opts[:format]}#{opts[:options]}" if opts[:options]
278
- options << ')'
279
- end
280
- table = if table.is_a?(::Sequel::Dataset)
281
- "(#{table.sql})"
282
- else
283
- literal(table)
284
- end
285
- sql = "COPY #{table} TO STDOUT#{options}"
286
- end
287
- synchronize(opts[:server]) do |conn|
288
- conn.execute(sql)
271
+ synchronize(opts[:server]) do |conn|
272
+ conn.execute(copy_table_sql(table, opts))
289
273
  begin
290
274
  if block_given?
291
275
  while buf = conn.get_copy_data
@@ -323,18 +307,6 @@ module Sequel
323
307
  # If a block is provided and :data option is not, this will yield to the block repeatedly.
324
308
  # The block should return a string, or nil to signal that it is finished.
325
309
  def copy_into(table, opts={})
326
- sql = "COPY #{literal(table)}"
327
- if cols = opts[:columns]
328
- sql << literal(Array(cols))
329
- end
330
- sql << " FROM STDIN"
331
- if opts[:options] || opts[:format]
332
- sql << " ("
333
- sql << "FORMAT #{opts[:format]}" if opts[:format]
334
- sql << "#{', ' if opts[:format]}#{opts[:options]}" if opts[:options]
335
- sql << ')'
336
- end
337
-
338
310
  data = opts[:data]
339
311
  data = Array(data) if data.is_a?(String)
340
312
 
@@ -344,8 +316,8 @@ module Sequel
344
316
  raise Error, "Must provide either a :data option or a block to copy_into"
345
317
  end
346
318
 
347
- synchronize(opts[:server]) do |conn|
348
- conn.execute(sql)
319
+ synchronize(opts[:server]) do |conn|
320
+ conn.execute(copy_into_sql(table, opts))
349
321
  begin
350
322
  if block_given?
351
323
  while buf = yield
@@ -380,6 +380,7 @@ module Sequel
380
380
  INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with insert into columns output values')
381
381
  SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct limit columns into from lock join where group having order compounds')
382
382
  UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with update limit table set output from where')
383
+ UPDATE_CLAUSE_METHODS_2000 = Dataset.clause_methods(:update, %w'update table set output from where')
383
384
  NOLOCK = ' WITH (NOLOCK)'.freeze
384
385
  UPDLOCK = ' WITH (UPDLOCK)'.freeze
385
386
  WILDCARD = LiteralString.new('*').freeze
@@ -801,10 +802,15 @@ module Sequel
801
802
  alias delete_output_sql output_sql
802
803
  alias update_output_sql output_sql
803
804
 
804
- # MSSQL supports the OUTPUT clause for UPDATE statements.
805
- # It also allows prepending a WITH clause.
805
+ # MSSQL supports the OUTPUT and TOP clause for UPDATE statements.
806
+ # It also allows prepending a WITH clause. For MSSQL 2000
807
+ # and below, exclude WITH and TOP.
806
808
  def update_clause_methods
807
- UPDATE_CLAUSE_METHODS
809
+ if is_2005_or_later?
810
+ UPDATE_CLAUSE_METHODS
811
+ else
812
+ UPDATE_CLAUSE_METHODS_2000
813
+ end
808
814
  end
809
815
 
810
816
  # Only include the primary table in the main update clause
@@ -49,6 +49,11 @@ module Sequel
49
49
  metadata_dataset.from(:tab).filter(:tname =>m.call(name), :tabtype => 'VIEW').count > 0
50
50
  end
51
51
 
52
+ # Oracle supports deferrable constraints.
53
+ def supports_deferrable_constraints?
54
+ true
55
+ end
56
+
52
57
  private
53
58
 
54
59
  # Handle Oracle specific ALTER TABLE SQL
@@ -224,6 +224,16 @@ module Sequel
224
224
  :postgres
225
225
  end
226
226
 
227
+ # Use PostgreSQL's DO syntax to execute an anonymous code block. The code should
228
+ # be the literal code string to use in the underlying procedural language. Options:
229
+ #
230
+ # :language :: The procedural language the code is written in. The PostgreSQL
231
+ # default is plpgsql. Can be specified as a string or a symbol.
232
+ def do(code, opts={})
233
+ language = opts[:language]
234
+ run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}"
235
+ end
236
+
227
237
  # Drops the function from the database. Arguments:
228
238
  # * name : name of the function to drop
229
239
  # * opts : options hash:
@@ -343,16 +353,17 @@ module Sequel
343
353
  join(:pg_index___ind, :indrelid=>:oid, im.call(table)=>:relname).
344
354
  join(:pg_class___indc, :oid=>:indexrelid).
345
355
  join(:pg_attribute___att, :attrelid=>:tab__oid, :attnum=>attnums).
356
+ left_join(:pg_constraint___con, :conname=>:indc__relname).
346
357
  filter(:indc__relkind=>'i', :ind__indisprimary=>false, :indexprs=>nil, :indpred=>nil, :indisvalid=>true).
347
358
  order(:indc__relname, SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(:ind__indkey, [x]), x]}, 32, :att__attnum)).
348
- select(:indc__relname___name, :ind__indisunique___unique, :att__attname___column)
359
+ select(:indc__relname___name, :ind__indisunique___unique, :att__attname___column, :con__condeferrable___deferrable)
349
360
 
350
361
  ds.join!(:pg_namespace___nsp, :oid=>:tab__relnamespace, :nspname=>schema.to_s) if schema
351
362
  ds.filter!(:indisready=>true, :indcheckxmin=>false) if server_version >= 80300
352
363
 
353
364
  indexes = {}
354
365
  ds.each do |r|
355
- i = indexes[m.call(r[:name])] ||= {:columns=>[], :unique=>r[:unique]}
366
+ i = indexes[m.call(r[:name])] ||= {:columns=>[], :unique=>r[:unique], :deferrable=>r[:deferrable]}
356
367
  i[:columns] << m.call(r[:column])
357
368
  end
358
369
  indexes
@@ -455,6 +466,16 @@ module Sequel
455
466
  server_version >= 90100
456
467
  end
457
468
 
469
+ # PostgreSQL 9.0+ supports some types of deferrable constraints beyond foreign key constraints.
470
+ def supports_deferrable_constraints?
471
+ server_version >= 90000
472
+ end
473
+
474
+ # PostgreSQL supports deferrable foreign key constraints.
475
+ def supports_deferrable_foreign_key_constraints?
476
+ true
477
+ end
478
+
458
479
  # PostgreSQL supports DROP TABLE IF EXISTS
459
480
  def supports_drop_table_if_exists?
460
481
  true
@@ -604,6 +625,42 @@ module Sequel
604
625
  end
605
626
  end
606
627
 
628
+ # SQL for doing fast table insert from stdin.
629
+ def copy_into_sql(table, opts)
630
+ sql = "COPY #{literal(table)}"
631
+ if cols = opts[:columns]
632
+ sql << literal(Array(cols))
633
+ end
634
+ sql << " FROM STDIN"
635
+ if opts[:options] || opts[:format]
636
+ sql << " ("
637
+ sql << "FORMAT #{opts[:format]}" if opts[:format]
638
+ sql << "#{', ' if opts[:format]}#{opts[:options]}" if opts[:options]
639
+ sql << ')'
640
+ end
641
+ sql
642
+ end
643
+
644
+ # SQL for doing fast table output to stdout.
645
+ def copy_table_sql(table, opts)
646
+ if table.is_a?(String)
647
+ return table
648
+ else
649
+ if opts[:options] || opts[:format]
650
+ options = " ("
651
+ options << "FORMAT #{opts[:format]}" if opts[:format]
652
+ options << "#{', ' if opts[:format]}#{opts[:options]}" if opts[:options]
653
+ options << ')'
654
+ end
655
+ table = if table.is_a?(::Sequel::Dataset)
656
+ "(#{table.sql})"
657
+ else
658
+ literal(table)
659
+ end
660
+ return "COPY #{table} TO STDOUT#{options}"
661
+ end
662
+ end
663
+
607
664
  # SQL statement to create database function.
608
665
  def create_function_sql(name, definition, opts={})
609
666
  args = opts[:args]