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 +4 -4
- data/CHANGELOG +18 -0
- data/README.rdoc +2 -2
- data/Rakefile +20 -34
- data/bin/sequel +1 -1
- data/doc/opening_databases.rdoc +1 -1
- data/doc/release_notes/4.5.0.txt +34 -0
- data/lib/sequel/adapters/db2.rb +2 -1
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc/db2.rb +5 -1
- data/lib/sequel/adapters/shared/db2.rb +1 -1
- data/lib/sequel/adapters/shared/mssql.rb +2 -0
- data/lib/sequel/adapters/shared/postgres.rb +25 -2
- data/lib/sequel/adapters/shared/sqlite.rb +15 -1
- data/lib/sequel/database/schema_methods.rb +1 -0
- data/lib/sequel/extensions/pg_array.rb +3 -0
- data/lib/sequel/extensions/pg_hstore.rb +3 -0
- data/lib/sequel/extensions/pg_inet.rb +3 -0
- data/lib/sequel/extensions/pg_interval.rb +3 -0
- data/lib/sequel/extensions/pg_json.rb +3 -0
- data/lib/sequel/extensions/pg_range.rb +3 -0
- data/lib/sequel/extensions/pg_row.rb +3 -0
- data/lib/sequel/model/base.rb +6 -2
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +92 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +5 -5
- data/spec/adapters/mssql_spec.rb +29 -2
- data/spec/adapters/postgres_spec.rb +46 -0
- data/spec/adapters/sqlite_spec.rb +7 -0
- data/spec/extensions/mssql_optimistic_locking_spec.rb +91 -0
- data/spec/model/model_spec.rb +9 -0
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab0c1f081253f4b43e1a46f2132e9b516e2a67ba
|
4
|
+
data.tar.gz: e6b2cd60d962bb23d4b6b03a5a1ef33f81762bd0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
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
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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.
|
29
|
+
opts.separator "For more information see http://sequel.jeremyevans.net"
|
30
30
|
opts.separator ""
|
31
31
|
opts.separator "Options:"
|
32
32
|
|
data/doc/opening_databases.rdoc
CHANGED
@@ -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.
|
data/lib/sequel/adapters/db2.rb
CHANGED
@@ -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
|
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)}
|
@@ -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?
|
1371
|
-
|
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
|
-
|
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'
|
data/lib/sequel/model/base.rb
CHANGED
@@ -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)
|
546
|
-
key
|
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
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
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
|
data/spec/adapters/db2_spec.rb
CHANGED
@@ -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 =
|
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 :
|
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 =
|
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"), :
|
58
|
-
@ds.select(:bin_string, :
|
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
|
|
data/spec/adapters/mssql_spec.rb
CHANGED
@@ -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
|
data/spec/model/model_spec.rb
CHANGED
@@ -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
|
+
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
|
+
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.
|
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:
|
685
|
+
rubyforge_project:
|
682
686
|
rubygems_version: 2.0.3
|
683
687
|
signing_key:
|
684
688
|
specification_version: 4
|