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 +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
|