sequel 4.4.0 → 4.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7006f0a52cb0de63ef52daa4eee4f0246b4dff47
4
- data.tar.gz: e936af319d149dcfdd4cffc7e183f79862e71d9f
3
+ metadata.gz: ab0c1f081253f4b43e1a46f2132e9b516e2a67ba
4
+ data.tar.gz: e6b2cd60d962bb23d4b6b03a5a1ef33f81762bd0
5
5
  SHA512:
6
- metadata.gz: f418f71c2c0bf871f80beb3948e03e5dfb531e7d14f612a78d9265cf7e7771781dd0cc0d23fe43c0539ff0b58512eeecac11c4a3c75aa93e887e9dd91fdbcc95
7
- data.tar.gz: b33de96a8d47aa303998c9ee560a351574a92176ef80adf23d3c749ebdbdfaf0762861cf6d454c72a89705a7b5c7e167161229dd12d8857d97b1ea090f0fcdd9
6
+ metadata.gz: d31e482c17d09830fc9895d61d3b9bc3f1bc527fe8939ea7856c98ce4b077ad842e73f8e7982708276898e6353b769fe281bdbb8becd82863b3f8e5e6c58fd7a
7
+ data.tar.gz: 4c9791b19cab0084aead4cf4a57a5c822d937ad449208c5f10d4b64a8cc7f89c0effd799b1761f540fd1778fd4d8c399034d716dabd3141b6aae1551062df8ac
data/CHANGELOG CHANGED
@@ -1,3 +1,21 @@
1
+ === 4.5.0 (2013-12-02)
2
+
3
+ * Support :on_commit=>(:drop|:delete_rows|:preserve_rows) options when creating temp tables on PostgreSQL (rosenfeld) (#737)
4
+
5
+ * Make Dataset#insert work on PostgreSQL if the table name is a SQL::PlaceholderLiteralString (jeremyevans) (#736)
6
+
7
+ * Copy unique constraints when emulating alter_table operations on SQLite (jeremyevans) (#735)
8
+
9
+ * Don't return clob column values as SQL::Blob instances in the db2 and ibmdb adapters unless use_clob_as_blob is true (jeremyevans)
10
+
11
+ * Make use_clob_as_blob false by default on DB2 (jeremyevans)
12
+
13
+ * Fix usage of Sequel::SQL::Blob objects as prepared statement arguments in jdbc/db2 adapter when use_clob_as_blob is false (jeremyevans)
14
+
15
+ * Add mssql_optimistic_locking plugin, using a timestamp/rowversion column to protect against concurrent updates (pinx, jeremyevans) (#731)
16
+
17
+ * Make Model.primary_key array immutable for composite keys (chanks) (#730)
18
+
1
19
  === 4.4.0 (2013-11-01)
2
20
 
3
21
  * Make Database#tables not show tables in the recycle bin on Oracle (jeremyevans) (#728)
data/README.rdoc CHANGED
@@ -18,12 +18,12 @@ toolkit for Ruby.
18
18
 
19
19
  == Resources
20
20
 
21
- * {Website}[http://sequel.rubyforge.org]
21
+ * {Website}[http://sequel.jeremyevans.net]
22
22
  * {Blog}[http://sequel.heroku.com]
23
23
  * {Source code}[http://github.com/jeremyevans/sequel]
24
24
  * {Bug tracking}[http://github.com/jeremyevans/sequel/issues]
25
25
  * {Google group}[http://groups.google.com/group/sequel-talk]
26
- * {RDoc}[http://sequel.rubyforge.org/rdoc]
26
+ * {RDoc}[http://sequel.jeremyevans.net/rdoc]
27
27
 
28
28
  To check out the source code:
29
29
 
data/Rakefile CHANGED
@@ -38,16 +38,11 @@ task :website do
38
38
  sh %{#{FileUtils::RUBY} www/make_www.rb}
39
39
  end
40
40
 
41
- desc "Update Non-RDoc section of sequel.rubyforge.org"
42
- task :website_rf_base=>[:website] do
43
- sh %{rsync -rt www/public/*.html rubyforge.org:/var/www/gforge-projects/sequel/}
44
- end
45
-
46
41
  ### RDoc
47
42
 
48
43
  RDOC_DEFAULT_OPTS = ["--line-numbers", "--inline-source", '--title', 'Sequel: The Database Toolkit for Ruby']
49
44
 
50
- allow_website_rdoc = begin
45
+ begin
51
46
  # Sequel uses hanna-nouveau for the website RDoc.
52
47
  # Due to bugs in older versions of RDoc, and the
53
48
  # fact that hanna-nouveau does not support RDoc 4,
@@ -55,9 +50,7 @@ allow_website_rdoc = begin
55
50
  gem 'rdoc', '= 3.12.2'
56
51
  gem 'hanna-nouveau'
57
52
  RDOC_DEFAULT_OPTS.concat(['-f', 'hanna'])
58
- true
59
53
  rescue Gem::LoadError
60
- false
61
54
  end
62
55
 
63
56
  rdoc_task_class = begin
@@ -80,32 +73,25 @@ if rdoc_task_class
80
73
  rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb doc/*.rdoc doc/release_notes/*.txt"
81
74
  end
82
75
 
83
- if allow_website_rdoc
84
- desc "Make rdoc for website"
85
- task :website_rdoc=>[:website_rdoc_main, :website_rdoc_adapters, :website_rdoc_plugins]
86
-
87
- rdoc_task_class.new(:website_rdoc_main) do |rdoc|
88
- rdoc.rdoc_dir = "www/public/rdoc"
89
- rdoc.options += RDOC_OPTS + %w'--no-ignore-invalid'
90
- 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 lib/sequel/extensions/core_extensions.rb"
91
- end
92
-
93
- rdoc_task_class.new(:website_rdoc_adapters) do |rdoc|
94
- rdoc.rdoc_dir = "www/public/rdoc-adapters"
95
- rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel --no-ignore-invalid'
96
- rdoc.rdoc_files.add %w"lib/sequel/adapters/**/*.rb"
97
- end
98
-
99
- rdoc_task_class.new(:website_rdoc_plugins) do |rdoc|
100
- rdoc.rdoc_dir = "www/public/rdoc-plugins"
101
- rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel --no-ignore-invalid'
102
- rdoc.rdoc_files.add %w"lib/sequel/{extensions,plugins}/**/*.rb doc/core_*"
103
- end
104
-
105
- desc "Update sequel.rubyforge.org"
106
- task :website_rf=>[:website, :website_rdoc] do
107
- sh %{rsync -rvt www/public/* rubyforge.org:/var/www/gforge-projects/sequel/}
108
- end
76
+ desc "Make rdoc for website"
77
+ task :website_rdoc=>[:website_rdoc_main, :website_rdoc_adapters, :website_rdoc_plugins]
78
+
79
+ rdoc_task_class.new(:website_rdoc_main) do |rdoc|
80
+ rdoc.rdoc_dir = "www/public/rdoc"
81
+ rdoc.options += RDOC_OPTS + %w'--no-ignore-invalid'
82
+ 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 lib/sequel/extensions/core_extensions.rb"
83
+ end
84
+
85
+ rdoc_task_class.new(:website_rdoc_adapters) do |rdoc|
86
+ rdoc.rdoc_dir = "www/public/rdoc-adapters"
87
+ rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel --no-ignore-invalid'
88
+ rdoc.rdoc_files.add %w"lib/sequel/adapters/**/*.rb"
89
+ end
90
+
91
+ rdoc_task_class.new(:website_rdoc_plugins) do |rdoc|
92
+ rdoc.rdoc_dir = "www/public/rdoc-plugins"
93
+ rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel --no-ignore-invalid'
94
+ rdoc.rdoc_files.add %w"lib/sequel/{extensions,plugins}/**/*.rb doc/core_*"
109
95
  end
110
96
  end
111
97
 
data/bin/sequel CHANGED
@@ -26,7 +26,7 @@ options = OptionParser.new do |opts|
26
26
  opts.separator " sequel postgres://localhost/my_blog"
27
27
  opts.separator " sequel config/database.yml"
28
28
  opts.separator ""
29
- opts.separator "For more information see http://sequel.rubyforge.org"
29
+ opts.separator "For more information see http://sequel.jeremyevans.net"
30
30
  opts.separator ""
31
31
  opts.separator "Options:"
32
32
 
@@ -28,7 +28,7 @@ You can use URI query parameters to specify options:
28
28
 
29
29
  You can also pass an additional option hash with the connection string:
30
30
 
31
- DB = Sequel.connect('postgres://localhost/blog' :user=>'user', :password=>'password')
31
+ DB = Sequel.connect('postgres://localhost/blog', :user=>'user', :password=>'password')
32
32
 
33
33
  You can also just use an options hash without a connection string. If you do this, you must
34
34
  provide the adapter to use:
@@ -0,0 +1,34 @@
1
+ = New Features
2
+
3
+ * An mssql_optimistic_locking plugin has been added. This is similar
4
+ to the regular optimistic_locking plugin, but instead of using an
5
+ integer lock column, it uses a timestamp/rowversion lock column.
6
+
7
+ * Database#create_table with the :temp=>true option on PostgreSQL now
8
+ supports an :on_commit option. This option can be set to :drop or
9
+ :delete_rows to either drop or empty the temporary table on
10
+ transaction commit.
11
+
12
+ = Other Improvements
13
+
14
+ * Dataset#insert no longer errors on PostgreSQL if the related table
15
+ is a placeholder literal string.
16
+
17
+ * Unique constraints are now copied when emulating alter_table
18
+ operations on SQLite.
19
+
20
+ * Clob column values are no longer returned as SQL::Blob instances
21
+ by the db2 and ibmdb adapters unless use_clob_as_blob is true.
22
+
23
+ * SQL::Blob objects now work correctly as prepared statement
24
+ arguments in the jdbc/db2 adapter if use_clob_as_blob is false.
25
+
26
+ = Backwards Compatibility
27
+
28
+ * The Model.primary_key array for models with composite keys is now
29
+ frozen.
30
+
31
+ * On DB2, use_clob_as_blob now defaults to false instead of true.
32
+
33
+ * Sequel no longer uses RubyForge. The Sequel website is now located
34
+ at http://sequel.jeremyevans.net.
@@ -31,7 +31,6 @@ module Sequel
31
31
  DB2CLI::SQL_TYPE_TIME => tt.method(:time),
32
32
  DB2CLI::SQL_DECIMAL => ::BigDecimal.method(:new)
33
33
  }
34
- DB2_TYPES[DB2CLI::SQL_CLOB] = DB2_TYPES[DB2CLI::SQL_BLOB]
35
34
 
36
35
  class Database < Sequel::Database
37
36
  include DatabaseMethods
@@ -217,6 +216,8 @@ module Sequel
217
216
  name, buflen, datatype, size, digits, nullable = db.checked_error("Could not describe column"){DB2CLI.SQLDescribeCol(sth, i, MAX_COL_SIZE)}
218
217
  pr = if datatype == DB2CLI::SQL_SMALLINT && convert && size <= 5 && digits <= 1
219
218
  cps[:boolean]
219
+ elsif datatype == DB2CLI::SQL_CLOB && Sequel::DB2.use_clob_as_blob
220
+ cps[DB2CLI::SQL_BLOB]
220
221
  else
221
222
  cps[datatype]
222
223
  end
@@ -25,7 +25,6 @@ module Sequel
25
25
  :time => ::Sequel.method(:string_to_time),
26
26
  :date => ::Sequel.method(:string_to_date)
27
27
  }
28
- DB2_TYPES[:clob] = DB2_TYPES[:blob]
29
28
 
30
29
  # Wraps an underlying connection to DB2 using IBM_DB.
31
30
  class Connection
@@ -435,7 +434,8 @@ module Sequel
435
434
  key = output_identifier(k)
436
435
  type = stmt.field_type(k).downcase.to_sym
437
436
  # decide if it is a smallint from precision
438
- type = :boolean if type ==:int && convert && stmt.field_precision(k) < 8
437
+ type = :boolean if type == :int && convert && stmt.field_precision(k) < 8
438
+ type = :blob if type == :clob && Sequel::DB2.use_clob_as_blob
439
439
  columns << [key, cps[type]]
440
440
  end
441
441
  cols = columns.map{|c| c.at(0)}
@@ -32,7 +32,11 @@ module Sequel
32
32
  def set_ps_arg(cps, arg, i)
33
33
  case arg
34
34
  when Sequel::SQL::Blob
35
- cps.setString(i, arg)
35
+ if ::Sequel::DB2.use_clob_as_blob
36
+ cps.setString(i, arg)
37
+ else
38
+ super
39
+ end
36
40
  else
37
41
  super
38
42
  end
@@ -2,7 +2,7 @@ Sequel.require 'adapters/utils/emulate_offset_with_row_number'
2
2
 
3
3
  module Sequel
4
4
  module DB2
5
- @use_clob_as_blob = true
5
+ @use_clob_as_blob = false
6
6
 
7
7
  class << self
8
8
  # Whether to use clob as the generic File type, true by default.
@@ -332,6 +332,8 @@ module Sequel
332
332
  :boolean
333
333
  when /\A(?:(?:small)?money)\z/io
334
334
  :decimal
335
+ when /\A(timestamp|rowversion)\z/io
336
+ :blob
335
337
  else
336
338
  super
337
339
  end
@@ -94,6 +94,9 @@ module Sequel
94
94
  FOREIGN_KEY_LIST_ON_DELETE_MAP = {'a'.freeze=>:no_action, 'r'.freeze=>:restrict, 'c'.freeze=>:cascade, 'n'.freeze=>:set_null, 'd'.freeze=>:set_default}.freeze
95
95
  POSTGRES_DEFAULT_RE = /\A(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))\z/
96
96
  UNLOGGED = 'UNLOGGED '.freeze
97
+ ON_COMMIT = {
98
+ :drop => 'DROP', :delete_rows => 'DELETE ROWS', :preserve_rows => 'PRESERVE ROWS',
99
+ }.freeze
97
100
 
98
101
  # SQL fragment for custom sequences (ones not created by serial primary key),
99
102
  # Returning the schema and literal form of the sequence name, by parsing
@@ -766,6 +769,10 @@ module Sequel
766
769
 
767
770
  # DDL statement for creating a table with the given name, columns, and options
768
771
  def create_table_prefix_sql(name, options)
772
+ if on_commit = options[:on_commit]
773
+ raise(Error, "can't provide :on_commit without :temp to create_table") unless options[:temp]
774
+ raise(Error, "unsupported on_commit option: #{on_commit.inspect}") unless ON_COMMIT.has_key? on_commit
775
+ end
769
776
  temp_or_unlogged_sql = if options[:temp]
770
777
  raise(Error, "can't provide both :temp and :unlogged to create_table") if options[:unlogged]
771
778
  temporary_table_sql
@@ -780,9 +787,20 @@ module Sequel
780
787
  if inherits = options[:inherits]
781
788
  sql << " INHERITS (#{Array(inherits).map{|t| quote_schema_table(t)}.join(', ')})"
782
789
  end
790
+ if on_commit = options[:on_commit]
791
+ sql << " ON COMMIT #{ON_COMMIT[on_commit]}"
792
+ end
783
793
  sql
784
794
  end
785
795
 
796
+ def create_table_as_sql(name, sql, options)
797
+ result = create_table_prefix_sql name, options
798
+ if on_commit = options[:on_commit]
799
+ result << " ON COMMIT #{ON_COMMIT[on_commit]}"
800
+ end
801
+ result << " AS #{sql}"
802
+ end
803
+
786
804
  # Use a PostgreSQL-specific create table generator
787
805
  def create_table_generator_class
788
806
  Postgres::CreateTableGenerator
@@ -1367,8 +1385,13 @@ module Sequel
1367
1385
 
1368
1386
  # Return the primary key to use for RETURNING in an INSERT statement
1369
1387
  def insert_pk
1370
- if (f = opts[:from]) && !f.empty? && (pk = db.primary_key(f.first))
1371
- Sequel::SQL::Identifier.new(pk)
1388
+ if (f = opts[:from]) && !f.empty?
1389
+ case t = f.first
1390
+ when Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier
1391
+ if pk = db.primary_key(t)
1392
+ Sequel::SQL::Identifier.new(pk)
1393
+ end
1394
+ end
1372
1395
  end
1373
1396
  end
1374
1397
 
@@ -97,7 +97,8 @@ module Sequel
97
97
  im = input_identifier_meth
98
98
  indexes = {}
99
99
  metadata_dataset.with_sql("PRAGMA index_list(?)", im.call(table)).each do |r|
100
- next if r[:name] =~ PRIMARY_KEY_INDEX_RE
100
+ # :only_autocreated internal option can be used to get only autocreated indexes
101
+ next if (!!(r[:name] =~ PRIMARY_KEY_INDEX_RE) ^ !!opts[:only_autocreated])
101
102
  indexes[m.call(r[:name])] = {:unique=>r[:unique].to_i==1}
102
103
  end
103
104
  indexes.each do |k, v|
@@ -389,6 +390,19 @@ module Sequel
389
390
  constraints.concat(fks.each{|h| h[:type] = :foreign_key})
390
391
  end
391
392
 
393
+ # Determine unique constraints and make sure the new columns have them
394
+ unique_columns = []
395
+ indexes(table, :only_autocreated=>true).each_value do |h|
396
+ unique_columns.concat(h[:columns]) if h[:columns].length == 1 && h[:unique]
397
+ end
398
+ unique_columns -= pks
399
+ unless unique_columns.empty?
400
+ unique_columns.map!{|c| quote_identifier(c)}
401
+ def_columns.each do |c|
402
+ c[:unique] = true if unique_columns.include?(quote_identifier(c[:name]))
403
+ end
404
+ end
405
+
392
406
  def_columns_str = (def_columns.map{|c| column_definition_sql(c)} + constraints.map{|c| constraint_definition_sql(c)}).join(', ')
393
407
  new_columns = old_columns.dup
394
408
  opts[:new_columns_proc].call(new_columns) if opts[:new_columns_proc]
@@ -155,6 +155,7 @@ module Sequel
155
155
  # :engine :: The table engine to use for the table.
156
156
  #
157
157
  # PostgreSQL specific options:
158
+ # :on_commit :: Either :preserve_rows (default), :drop or :delete_rows.
158
159
  # :unlogged :: Create the table as an unlogged table.
159
160
  # :inherits :: Inherit from a different tables. An array can be
160
161
  # specified to inherit from multiple tables.
@@ -36,6 +36,9 @@
36
36
  #
37
37
  # DB.extension :pg_array
38
38
  #
39
+ # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
40
+ # for details on using postgres array columns in CREATE/ALTER TABLE statements.
41
+ #
39
42
  # If you are not using the native postgres adapter and are using array
40
43
  # types as model column values you probably should use the
41
44
  # typecast_on_load plugin if the column values are returned as a
@@ -75,6 +75,9 @@
75
75
  #
76
76
  # DB.extension :pg_hstore
77
77
  #
78
+ # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
79
+ # for details on using hstore columns in CREATE/ALTER TABLE statements.
80
+ #
78
81
  # If you are not using the native postgres adapter and are using hstore
79
82
  # types as model column values you probably should use the
80
83
  # typecast_on_load plugin if the column values are returned as a
@@ -25,6 +25,9 @@
25
25
  # addresses, so these will still be returned as strings. The exception
26
26
  # to this is that the pg_array extension integration will recognize
27
27
  # macaddr[] types return them as arrays of strings.
28
+ #
29
+ # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
30
+ # for details on using inet/cidr columns in CREATE/ALTER TABLE statements.
28
31
 
29
32
  require 'ipaddr'
30
33
  Sequel.require 'adapters/utils/pg_types'
@@ -31,6 +31,9 @@
31
31
  # very simple, and is only designed to parse PostgreSQL's default output
32
32
  # format, it is not designed to support all input formats that PostgreSQL
33
33
  # supports.
34
+ #
35
+ # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
36
+ # for details on using interval columns in CREATE/ALTER TABLE statements.
34
37
 
35
38
  require 'active_support/duration'
36
39
  Sequel.require 'adapters/utils/pg_types'
@@ -46,6 +46,9 @@
46
46
  # types as model column values you probably should use the
47
47
  # pg_typecast_on_load plugin if the column values are returned as a string.
48
48
  #
49
+ # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
50
+ # for details on using json columns in CREATE/ALTER TABLE statements.
51
+ #
49
52
  # This extension integrates with the pg_array extension. If you plan
50
53
  # to use the json[] type, load the pg_array extension before the
51
54
  # pg_json extension:
@@ -49,6 +49,9 @@
49
49
  # types as model column values you probably should use the
50
50
  # pg_typecast_on_load plugin if the column values are returned as a string.
51
51
  #
52
+ # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
53
+ # for details on using range type columns in CREATE/ALTER TABLE statements.
54
+ #
52
55
  # This extension integrates with the pg_array extension. If you plan
53
56
  # to use arrays of range types, load the pg_array extension before the
54
57
  # pg_range extension:
@@ -78,6 +78,9 @@
78
78
  # types as model column values you probably should use the
79
79
  # pg_typecast_on_load plugin if the column values are returned as a string.
80
80
  #
81
+ # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
82
+ # for details on using row type columns in CREATE/ALTER TABLE statements.
83
+ #
81
84
  # This extension requires both the strscan and delegate libraries.
82
85
 
83
86
  require 'delegate'
@@ -542,8 +542,12 @@ module Sequel
542
542
  # end
543
543
  def set_primary_key(key)
544
544
  clear_setter_methods_cache
545
- if key.is_a?(Array) && key.length < 2
546
- key = key.first
545
+ if key.is_a?(Array)
546
+ if key.length < 2
547
+ key = key.first
548
+ else
549
+ key = key.dup.freeze
550
+ end
547
551
  end
548
552
  self.simple_pk = if key && !key.is_a?(Array)
549
553
  (@dataset || db).literal(key)
@@ -0,0 +1,92 @@
1
+ module Sequel
2
+ module Plugins
3
+ # This plugin implements optimistic locking mechanism on Microsoft SQL Server
4
+ # using a timestamp/rowversion column to ensure that concurrent updates are
5
+ # detected and previous changes are not automatically overridden. This is
6
+ # best implemented by a code example:
7
+ #
8
+ # class Person < Sequel::Model
9
+ # plugin :mssql_optimistic_locking
10
+ # end
11
+ # p1 = Person[1]
12
+ # p2 = Person[1]
13
+ # p1.update(:name=>'Jim') # works
14
+ # p2.update(:name=>'Bob') # raises Sequel::NoExistingObject
15
+ #
16
+ # In order for this plugin to work, you need to make sure that the database
17
+ # table has a column of timestamp or rowversion. The plugin uses a default
18
+ # name of timestamp for this columns, but you can override that using the
19
+ # :lock_column option:
20
+ #
21
+ # plugin :mssql_optimistic_locking, :lock_column=>:column_name
22
+ #
23
+ # This plugin relies on the instance_filters plugin.
24
+ module MssqlOptimisticLocking
25
+ # Load the instance_filters plugin into the model.
26
+ def self.apply(model, opts=OPTS)
27
+ model.plugin :instance_filters
28
+ end
29
+
30
+ # Set the lock_column to the :lock_column option (default: :timestamp)
31
+ def self.configure(model, opts=OPTS)
32
+ model.lock_column = opts[:lock_column] || :timestamp
33
+ end
34
+
35
+ module ClassMethods
36
+ # The timestamp/rowversion column containing the version for the current row.
37
+ attr_accessor :lock_column
38
+
39
+ Plugins.inherited_instance_variables(self, :@lock_column=>nil)
40
+ end
41
+
42
+ module InstanceMethods
43
+ # Add the lock column instance filter to the object before destroying it.
44
+ def before_destroy
45
+ lock_column_instance_filter
46
+ super
47
+ end
48
+
49
+ # Add the lock column instance filter to the object before updating it.
50
+ def before_update
51
+ lock_column_instance_filter
52
+ super
53
+ end
54
+
55
+ private
56
+
57
+ # Add the lock column instance filter to the object.
58
+ def lock_column_instance_filter
59
+ lc = model.lock_column
60
+ instance_filter(lc=>Sequel.blob(send(lc)))
61
+ end
62
+
63
+ # Clear the instance filters when refreshing, so that attempting to
64
+ # refresh after a failed save removes the previous lock column filter
65
+ # (the new one will be added before updating).
66
+ def _refresh(ds)
67
+ clear_instance_filters
68
+ super
69
+ end
70
+
71
+ # Remove the lock column from the columns to update.
72
+ # SQL Server automatically updates the lock column value, and does not like
73
+ # it to be assigned.
74
+ def _save_update_all_columns_hash
75
+ v = @values.dup
76
+ Array(primary_key).each{|x| v.delete(x) unless changed_columns.include?(x)}
77
+ v.delete(model.lock_column)
78
+ v
79
+ end
80
+
81
+ # Add an OUTPUT clause to fetch the updated timestamp when updating the row.
82
+ def _update_without_checking(columns)
83
+ ds = _update_dataset
84
+ lc = model.lock_column
85
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel.qualify(:inserted, lc)]).update_sql(columns))).all
86
+ values[lc] = rows.first[lc] unless rows.empty?
87
+ rows.length
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 4
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 4
6
+ MINOR = 5
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0
@@ -30,12 +30,12 @@ end
30
30
 
31
31
  describe "Simple Dataset operations" do
32
32
  before(:all) do
33
- Sequel::DB2.use_clob_as_blob = false
33
+ Sequel::DB2.use_clob_as_blob = true
34
34
  DB.create_table!(:items) do
35
35
  Integer :id, :primary_key => true
36
36
  Integer :number
37
37
  column :bin_string, 'varchar(20) for bit data'
38
- column :bin_blob, 'blob'
38
+ column :bin_clob, 'clob'
39
39
  end
40
40
  @ds = DB[:items]
41
41
  end
@@ -43,7 +43,7 @@ describe "Simple Dataset operations" do
43
43
  @ds.delete
44
44
  end
45
45
  after(:all) do
46
- Sequel::DB2.use_clob_as_blob = true
46
+ Sequel::DB2.use_clob_as_blob = false
47
47
  DB.drop_table(:items)
48
48
  end
49
49
 
@@ -54,8 +54,8 @@ describe "Simple Dataset operations" do
54
54
  end
55
55
 
56
56
  specify "should insert into binary columns" do
57
- @ds.insert(:id => 1, :bin_string => Sequel.blob("\1"), :bin_blob => Sequel.blob("\2"))
58
- @ds.select(:bin_string, :bin_blob).first.should == {:bin_string => "\1", :bin_blob => "\2"}
57
+ @ds.insert(:id => 1, :bin_string => Sequel.blob("\1"), :bin_clob => Sequel.blob("\2"))
58
+ @ds.select(:bin_string, :bin_clob).first.should == {:bin_string => "\1", :bin_clob => "\2"}
59
59
  end
60
60
  end
61
61
 
@@ -607,8 +607,8 @@ describe "Database#foreign_key_list" do
607
607
  end
608
608
  end
609
609
  after(:all) do
610
- DB.drop_table :vendor__mapping
611
- DB.drop_table :vendor__vendors
610
+ DB.drop_table? :vendor__mapping
611
+ DB.drop_table? :vendor__vendors
612
612
  DB.execute_ddl "drop schema vendor"
613
613
  end
614
614
  it "should support mixed schema bound tables" do
@@ -616,3 +616,30 @@ describe "Database#foreign_key_list" do
616
616
  end
617
617
  end
618
618
  end
619
+
620
+ describe "MSSQL optimistic locking plugin" do
621
+ before do
622
+ @db = DB
623
+ @db.create_table! :items do
624
+ primary_key :id
625
+ String :name, :size => 20
626
+ column :timestamp, 'timestamp'
627
+ end
628
+ end
629
+ after do
630
+ @db.drop_table?(:items)
631
+ end
632
+
633
+ it "should not allow stale updates" do
634
+ c = Class.new(Sequel::Model(:items))
635
+ c.plugin :mssql_optimistic_locking
636
+ o = c.create(:name=>'test')
637
+ o2 = c.first
638
+ ts = o.timestamp
639
+ ts.should_not be_nil
640
+ o.name = 'test2'
641
+ o.save
642
+ o.timestamp.should_not == ts
643
+ proc{o2.save}.should raise_error(Sequel::NoExistingObject)
644
+ end
645
+ end unless DB.adapter_scheme == :odbc
@@ -27,6 +27,46 @@ describe "PostgreSQL", '#create_table' do
27
27
  end
28
28
  end
29
29
 
30
+ specify "temporary table should support :on_commit option" do
31
+ @db.drop_table?(:some_table)
32
+ @db.transaction do
33
+ @db.create_table(:some_table, :temp => true, :on_commit => :drop){text :name}
34
+ end
35
+ @db.table_exists?(:some_table).should be_false
36
+
37
+ @db.transaction do
38
+ @db.create_table(:some_table, :temp => true, :on_commit => :delete_rows){text :name}
39
+ @db[:some_table].insert('a')
40
+ end
41
+ @db.table_exists?(:some_table).should be_true
42
+ @db[:some_table].empty?.should be_true
43
+
44
+ @db.drop_table(:some_table)
45
+ @db.transaction do
46
+ @db.create_table(:some_table, :temp => true, :on_commit => :preserve_rows){text :name}
47
+ @db[:some_table].insert('a')
48
+ end
49
+ @db.table_exists?(:some_table).should be_true
50
+ @db[:some_table].count.should == 1
51
+ @db.drop_table(:some_table)
52
+ end
53
+
54
+ specify "temporary table should accept :on_commit with :as option" do
55
+ @db.drop_table?(:some_table)
56
+ @db.transaction do
57
+ @db.create_table(:some_table, :temp => true, :on_commit => :drop, :as => 'select 1')
58
+ end
59
+ @db.table_exists?(:some_table).should be_false
60
+ end
61
+
62
+ specify ":on_commit should raise error if not used on a temporary table" do
63
+ proc{@db.create_table(:some_table, :on_commit => :drop)}.should raise_error(Sequel::Error)
64
+ end
65
+
66
+ specify ":on_commit should raise error if given unsupported value" do
67
+ proc{@db.create_table(:some_table, :temp => true, :on_commit => :unsupported){text :name}}.should raise_error(Sequel::Error)
68
+ end
69
+
30
70
  specify "should create an unlogged table" do
31
71
  @db.create_table(:unlogged_dolls, :unlogged => true){text :name}
32
72
  check_sqls do
@@ -161,6 +201,12 @@ describe "A PostgreSQL database" do
161
201
  specify "should return uuid fields as strings" do
162
202
  @db.get(Sequel.cast('550e8400-e29b-41d4-a716-446655440000', :uuid)).should == '550e8400-e29b-41d4-a716-446655440000'
163
203
  end
204
+
205
+ specify "should handle inserts with placeholder literal string tables" do
206
+ ds = @db.from(Sequel.lit('?', :testfk))
207
+ ds.insert(:id=>1)
208
+ ds.select_map(:id).should == [1]
209
+ end
164
210
  end
165
211
 
166
212
  describe "A PostgreSQL database with domain types" do
@@ -640,4 +640,11 @@ describe "A SQLite database" do
640
640
  @db.transaction_mode.should == :immediate
641
641
  proc {@db.transaction(:mode => :invalid) {}}.should raise_error(Sequel::Error)
642
642
  end
643
+
644
+ specify "should keep unique constraints when copying tables" do
645
+ @db.alter_table(:test2){add_unique_constraint :name}
646
+ @db.alter_table(:test2){drop_column :value}
647
+ @db[:test2].insert(:name=>'a')
648
+ proc{@db[:test2].insert(:name=>'a')}.should raise_error(Sequel::ConstraintViolation)
649
+ end
643
650
  end
@@ -0,0 +1,91 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper')
2
+
3
+ describe "MSSSQL optimistic locking plugin" do
4
+ before do
5
+ @db = Sequel.mock(:host=>'mssql')
6
+ @c = Class.new(Sequel::Model(@db[:items]))
7
+ @c.columns :id, :name, :timestamp
8
+ @c.plugin :mssql_optimistic_locking
9
+ @o = @c.load(:id=>1, :name=>'a', :timestamp=>'1234')
10
+ @db.sqls
11
+ end
12
+
13
+ it "should not include the lock column when updating" do
14
+ @db.fetch = [[{:timestamp=>'2345'}]]
15
+ @o.save
16
+ @db.sqls.should == ["UPDATE TOP (1) items SET name = 'a' OUTPUT inserted.timestamp WHERE ((id = 1) AND (timestamp = 0x31323334))"]
17
+ end
18
+
19
+ it "should automatically update lock column using new value from database" do
20
+ @db.fetch = [[{:timestamp=>'2345'}]]
21
+ @o.save
22
+ @o.timestamp.should == '2345'
23
+ end
24
+
25
+ it "should raise error when updating stale object" do
26
+ @db.fetch = []
27
+ @o.timestamp = '2345'
28
+ proc{@o.save}.should raise_error(Sequel::NoExistingObject)
29
+ @o.timestamp.should == '2345'
30
+ @db.sqls.should == ["UPDATE TOP (1) items SET name = 'a' OUTPUT inserted.timestamp WHERE ((id = 1) AND (timestamp = 0x32333435))"]
31
+ end
32
+
33
+ it "should raise error when destroying stale object" do
34
+ @db.numrows = 0
35
+ @o.timestamp = '2345'
36
+ proc{@o.destroy}.should raise_error(Sequel::NoExistingObject)
37
+ @db.sqls.should == ["DELETE FROM items WHERE ((id = 1) AND (timestamp = 0x32333435))"]
38
+ end
39
+
40
+ it "should allow refresh after failed save" do
41
+ @db.fetch = []
42
+ @o.timestamp = '2345'
43
+ proc{@o.save}.should raise_error(Sequel::NoExistingObject)
44
+ @db.fetch = {:id=>1, :name=>'a', :timestamp=>'2345'}
45
+ @o.refresh
46
+ @db.sqls
47
+ @o.save
48
+ @db.sqls.should == ["UPDATE TOP (1) items SET name = 'a' OUTPUT inserted.timestamp WHERE ((id = 1) AND (timestamp = 0x32333435))"]
49
+ end
50
+
51
+ specify "should allow changing the lock column via model.lock_column=" do
52
+ @c = Class.new(Sequel::Model(@db[:items]))
53
+ @c.columns :id, :name, :lv
54
+ @c.plugin :mssql_optimistic_locking
55
+ @c.lock_column = :lv
56
+ @o = @c.load(:id=>1, :name=>'a', :lv=>'1234')
57
+ @db.sqls
58
+ @db.fetch = []
59
+ proc{@o.save}.should raise_error(Sequel::NoExistingObject)
60
+ @o.lv.should == '1234'
61
+ @db.sqls.should == ["UPDATE TOP (1) items SET name = 'a' OUTPUT inserted.lv WHERE ((id = 1) AND (lv = 0x31323334))"]
62
+ @o = @c.load(:id=>1, :name=>'a', :lv=>'1234')
63
+ @db.fetch = {:lv=>'2345'}
64
+ @o.save
65
+ @o.lv.should == '2345'
66
+ end
67
+
68
+ specify "should allow changing the lock column via plugin option" do
69
+ @c = Class.new(Sequel::Model(@db[:items]))
70
+ @c.columns :id, :name, :lv
71
+ @c.plugin :mssql_optimistic_locking, :lock_column=>:lv
72
+ @o = @c.load(:id=>1, :name=>'a', :lv=>'1234')
73
+ @db.sqls
74
+ @db.fetch = []
75
+ proc{@o.save}.should raise_error(Sequel::NoExistingObject)
76
+ @o.lv.should == '1234'
77
+ @db.sqls.should == ["UPDATE TOP (1) items SET name = 'a' OUTPUT inserted.lv WHERE ((id = 1) AND (lv = 0x31323334))"]
78
+ @o = @c.load(:id=>1, :name=>'a', :lv=>'1234')
79
+ @db.fetch = {:lv=>'2345'}
80
+ @o.save
81
+ @o.lv.should == '2345'
82
+ end
83
+
84
+ specify "should work when subclassing" do
85
+ c = Class.new(@c)
86
+ o = c.load(:id=>1, :name=>'a', :timestamp=>'1234')
87
+ @db.fetch = [[{:timestamp=>'2345'}]]
88
+ o.save
89
+ @db.sqls.should == ["UPDATE TOP (1) items SET name = 'a' OUTPUT inserted.timestamp WHERE ((id = 1) AND (timestamp = 0x31323334))"]
90
+ end
91
+ end
@@ -686,6 +686,15 @@ describe "Model.db_schema" do
686
686
  @c.db_schema.should == {:x=>{:primary_key=>true}, :y=>{:primary_key=>true}}
687
687
  @c.primary_key.should == [:x, :y]
688
688
  end
689
+
690
+ specify "should set an immutable composite primary key based on the schema" do
691
+ ds = @dataset
692
+ d = ds.db
693
+ def d.schema(table, *opts) [[:x, {:primary_key=>true}], [:y, {:primary_key=>true}]] end
694
+ @c.dataset = ds
695
+ @c.primary_key.should == [:x, :y]
696
+ proc{@c.primary_key.pop}.should raise_error
697
+ end
689
698
 
690
699
  specify "should automatically set no primary key based on the schema" do
691
700
  ds = @dataset
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.0
4
+ version: 4.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-01 00:00:00.000000000 Z
11
+ date: 2013-12-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: The Database Toolkit for Ruby
14
14
  email: code@jeremyevans.net
@@ -120,6 +120,7 @@ extra_rdoc_files:
120
120
  - doc/release_notes/4.2.0.txt
121
121
  - doc/release_notes/4.3.0.txt
122
122
  - doc/release_notes/4.4.0.txt
123
+ - doc/release_notes/4.5.0.txt
123
124
  files:
124
125
  - MIT-LICENSE
125
126
  - CHANGELOG
@@ -227,6 +228,7 @@ files:
227
228
  - doc/release_notes/4.2.0.txt
228
229
  - doc/release_notes/4.3.0.txt
229
230
  - doc/release_notes/4.4.0.txt
231
+ - doc/release_notes/4.5.0.txt
230
232
  - spec/adapters/firebird_spec.rb
231
233
  - spec/adapters/informix_spec.rb
232
234
  - spec/adapters/mssql_spec.rb
@@ -356,6 +358,7 @@ files:
356
358
  - spec/extensions/inflector_spec.rb
357
359
  - spec/extensions/association_proxies_spec.rb
358
360
  - spec/extensions/table_select_spec.rb
361
+ - spec/extensions/mssql_optimistic_locking_spec.rb
359
362
  - spec/integration/associations_test.rb
360
363
  - spec/integration/database_test.rb
361
364
  - spec/integration/dataset_test.rb
@@ -648,11 +651,12 @@ files:
648
651
  - lib/sequel/plugins/blacklist_security.rb
649
652
  - lib/sequel/plugins/pg_array_associations.rb
650
653
  - lib/sequel/plugins/table_select.rb
654
+ - lib/sequel/plugins/mssql_optimistic_locking.rb
651
655
  - lib/sequel/timezones.rb
652
656
  - lib/sequel/deprecated.rb
653
657
  - lib/sequel/ast_transformer.rb
654
658
  - lib/sequel/no_core_ext.rb
655
- homepage: http://sequel.rubyforge.org
659
+ homepage: http://sequel.jeremyevans.net
656
660
  licenses:
657
661
  - MIT
658
662
  metadata: {}
@@ -678,7 +682,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
678
682
  - !ruby/object:Gem::Version
679
683
  version: '0'
680
684
  requirements: []
681
- rubyforge_project: sequel
685
+ rubyforge_project:
682
686
  rubygems_version: 2.0.3
683
687
  signing_key:
684
688
  specification_version: 4