sequel 4.4.0 → 4.5.0

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