sequel 5.3.0 → 5.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG +30 -0
- data/bin/sequel +13 -0
- data/doc/cheat_sheet.rdoc +1 -0
- data/doc/dataset_filtering.rdoc +1 -1
- data/doc/querying.rdoc +8 -11
- data/doc/release_notes/5.4.0.txt +80 -0
- data/doc/testing.rdoc +2 -0
- data/lib/sequel/adapters/shared/db2.rb +6 -5
- data/lib/sequel/adapters/shared/mssql.rb +5 -8
- data/lib/sequel/adapters/shared/mysql.rb +4 -8
- data/lib/sequel/adapters/shared/oracle.rb +1 -1
- data/lib/sequel/adapters/shared/postgres.rb +5 -3
- data/lib/sequel/adapters/shared/sqlanywhere.rb +1 -6
- data/lib/sequel/adapters/shared/sqlite.rb +2 -0
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/schema_methods.rb +10 -1
- data/lib/sequel/dataset/query.rb +1 -2
- data/lib/sequel/extensions/date_arithmetic.rb +27 -10
- data/lib/sequel/extensions/datetime_parse_to_time.rb +37 -0
- data/lib/sequel/extensions/index_caching.rb +107 -0
- data/lib/sequel/extensions/null_dataset.rb +3 -1
- data/lib/sequel/extensions/pg_timestamptz.rb +26 -0
- data/lib/sequel/model/base.rb +2 -2
- data/lib/sequel/plugins/class_table_inheritance.rb +11 -3
- data/lib/sequel/plugins/json_serializer.rb +2 -2
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +1 -1
- data/spec/adapters/spec_helper.rb +3 -0
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/bin_spec.rb +9 -0
- data/spec/core/connection_pool_spec.rb +2 -2
- data/spec/core/dataset_spec.rb +1 -6
- data/spec/extensions/class_table_inheritance_spec.rb +52 -2
- data/spec/extensions/date_arithmetic_spec.rb +15 -1
- data/spec/extensions/datetime_parse_to_time_spec.rb +169 -0
- data/spec/extensions/index_caching_spec.rb +66 -0
- data/spec/extensions/json_serializer_spec.rb +5 -0
- data/spec/extensions/null_dataset_spec.rb +5 -0
- data/spec/extensions/pg_extended_date_support_spec.rb +4 -0
- data/spec/extensions/pg_timestamptz_spec.rb +17 -0
- data/spec/extensions/xml_serializer_spec.rb +7 -0
- data/spec/integration/dataset_test.rb +6 -0
- data/spec/integration/prepared_statement_test.rb +1 -1
- data/spec/integration/schema_test.rb +19 -17
- data/spec/integration/spec_helper.rb +4 -0
- data/spec/model/record_spec.rb +28 -0
- metadata +11 -3
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# This switches the default parsing of strings into Time values
|
4
|
+
# from using Time.parse to using DateTime.parse.to_time. This
|
5
|
+
# fixes issues when the times being parsed have no timezone
|
6
|
+
# information, the implicit timezone for the Database instance
|
7
|
+
# is set to +:utc+, and the timestamps being used include values
|
8
|
+
# not valid in the local timezone, such as during a daylight
|
9
|
+
# savings time switch.
|
10
|
+
#
|
11
|
+
# To load the extension:
|
12
|
+
#
|
13
|
+
# Sequel.extension :datetime_parse_to_time
|
14
|
+
|
15
|
+
#
|
16
|
+
module Sequel::DateTimeParseToTime
|
17
|
+
private
|
18
|
+
|
19
|
+
# Use DateTime.parse.to_time to do the conversion if the input a string and is assumed to
|
20
|
+
# be in UTC and there is no offset information in the string.
|
21
|
+
def convert_input_timestamp(v, input_timezone)
|
22
|
+
if v.is_a?(String) && datetime_class == Time && input_timezone == :utc && !Date._parse(v).has_key?(:offset)
|
23
|
+
t = DateTime.parse(v).to_time
|
24
|
+
case application_timezone
|
25
|
+
when nil, :local
|
26
|
+
t = t.localtime
|
27
|
+
end
|
28
|
+
t
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
rescue => e
|
33
|
+
raise convert_exception_class(e, Sequel::InvalidValue)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Sequel.extend(Sequel::DateTimeParseToTime)
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The index_caching extension adds a few methods to Sequel::Database
|
4
|
+
# that make it easy to dump information about database indexes to a file,
|
5
|
+
# and load it from that file. Loading index information from a
|
6
|
+
# dumped file is faster than parsing it from the database, so this
|
7
|
+
# can save bootup time for applications with large numbers of index.
|
8
|
+
#
|
9
|
+
# Basic usage in application code:
|
10
|
+
#
|
11
|
+
# DB = Sequel.connect('...')
|
12
|
+
# DB.extension :index_caching
|
13
|
+
# DB.load_index_cache('/path/to/index_cache.dump')
|
14
|
+
#
|
15
|
+
# # load model files
|
16
|
+
#
|
17
|
+
# Then, whenever database indicies are modified, write a new cached
|
18
|
+
# file. You can do that with <tt>bin/sequel</tt>'s -X option:
|
19
|
+
#
|
20
|
+
# bin/sequel -X /path/to/index_cache.dump postgres://...
|
21
|
+
#
|
22
|
+
# Alternatively, if you don't want to dump the index information for
|
23
|
+
# all tables, and you don't worry about race conditions, you can
|
24
|
+
# choose to use the following in your application code:
|
25
|
+
#
|
26
|
+
# DB = Sequel.connect('...')
|
27
|
+
# DB.extension :index_caching
|
28
|
+
# DB.load_index_cache?('/path/to/index_cache.dump')
|
29
|
+
#
|
30
|
+
# # load model files
|
31
|
+
#
|
32
|
+
# DB.dump_index_cache?('/path/to/index_cache.dump')
|
33
|
+
#
|
34
|
+
# With this method, you just have to delete the index dump file if
|
35
|
+
# the schema is modified, and the application will recreate it for you
|
36
|
+
# using just the tables that your models use.
|
37
|
+
#
|
38
|
+
# Note that it is up to the application to ensure that the dumped
|
39
|
+
# index cache reflects the current state of the database. Sequel
|
40
|
+
# does no checking to ensure this, as checking would take time and the
|
41
|
+
# purpose of this code is to take a shortcut.
|
42
|
+
#
|
43
|
+
# The index cache is dumped in Marshal format, since it is the fastest
|
44
|
+
# and it handles all ruby objects used in the indexes hash. Because of this,
|
45
|
+
# you should not attempt to load from an untrusted file.
|
46
|
+
#
|
47
|
+
# Related module: Sequel::IndexCaching
|
48
|
+
|
49
|
+
#
|
50
|
+
module Sequel
|
51
|
+
module IndexCaching
|
52
|
+
# Set index cache to the empty hash.
|
53
|
+
def self.extended(db)
|
54
|
+
db.instance_variable_set(:@indexes, {})
|
55
|
+
end
|
56
|
+
|
57
|
+
# Remove the index cache for the given schema name
|
58
|
+
def remove_cached_schema(table)
|
59
|
+
k = quote_schema_table(table)
|
60
|
+
Sequel.synchronize{@indexes.delete(k)}
|
61
|
+
super
|
62
|
+
end
|
63
|
+
|
64
|
+
# Dump the index cache to the filename given in Marshal format.
|
65
|
+
def dump_index_cache(file)
|
66
|
+
File.open(file, 'wb'){|f| f.write(Marshal.dump(@indexes))}
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
# Dump the index cache to the filename given unless the file
|
71
|
+
# already exists.
|
72
|
+
def dump_index_cache?(file)
|
73
|
+
dump_index_cache(file) unless File.exist?(file)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Replace the index cache with the data from the given file, which
|
77
|
+
# should be in Marshal format.
|
78
|
+
def load_index_cache(file)
|
79
|
+
@indexes = Marshal.load(File.read(file))
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
# Replace the index cache with the data from the given file if the
|
84
|
+
# file exists.
|
85
|
+
def load_index_cache?(file)
|
86
|
+
load_index_cache(file) if File.exist?(file)
|
87
|
+
end
|
88
|
+
|
89
|
+
# If no options are provided and there is cached index information for
|
90
|
+
# the table, return the cached information instead of querying the
|
91
|
+
# database.
|
92
|
+
def indexes(table, opts=OPTS)
|
93
|
+
return super unless opts.empty?
|
94
|
+
|
95
|
+
quoted_name = literal(table)
|
96
|
+
if v = Sequel.synchronize{@indexes[quoted_name]}
|
97
|
+
return v
|
98
|
+
end
|
99
|
+
|
100
|
+
result = super
|
101
|
+
Sequel.synchronize{@indexes[quoted_name] = result}
|
102
|
+
result
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
Database.register_extension(:index_caching, IndexCaching)
|
107
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The pg_timestamptz extension changes the default timestamp
|
4
|
+
# type for the database to be +timestamptz+ (+timestamp with time zone+)
|
5
|
+
# instead of +timestamp+ (+timestamp without time zone+). This is
|
6
|
+
# recommended if you are dealing with multiple timezones in your application.
|
7
|
+
#
|
8
|
+
# To load the extension into the database:
|
9
|
+
#
|
10
|
+
# DB.extension :pg_timestamptz
|
11
|
+
#
|
12
|
+
# Related module: Sequel::Postgres::Timestamptz
|
13
|
+
|
14
|
+
#
|
15
|
+
module Sequel
|
16
|
+
module Postgres
|
17
|
+
module Timestamptz
|
18
|
+
# Use timestamptz by default for generic timestamp value.
|
19
|
+
def type_literal_generic_datetime(column)
|
20
|
+
:timestamptz
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Database.register_extension(:pg_timestamptz, Postgres::Timestamptz)
|
26
|
+
end
|
data/lib/sequel/model/base.rb
CHANGED
@@ -1635,8 +1635,8 @@ module Sequel
|
|
1635
1635
|
# the record should be refreshed from the database.
|
1636
1636
|
def _insert
|
1637
1637
|
ds = _insert_dataset
|
1638
|
-
if _use_insert_select?(ds) && (h = _insert_select_raw(ds))
|
1639
|
-
_save_set_values(h)
|
1638
|
+
if _use_insert_select?(ds) && !(h = _insert_select_raw(ds)).nil?
|
1639
|
+
_save_set_values(h) if h
|
1640
1640
|
nil
|
1641
1641
|
else
|
1642
1642
|
iid = _insert_raw(ds)
|
@@ -196,6 +196,8 @@ module Sequel
|
|
196
196
|
# :key_chooser :: proc returning key for the provided model instance
|
197
197
|
# :table_map :: Hash with class name symbols keys mapping to table name symbol values.
|
198
198
|
# Overrides implicit table names.
|
199
|
+
# :ignore_subclass_columns :: Array with column names as symbols that are ignored
|
200
|
+
# on all sub-classes.
|
199
201
|
def self.configure(model, opts = OPTS)
|
200
202
|
SingleTableInheritance.configure model, opts[:key], opts
|
201
203
|
|
@@ -206,6 +208,7 @@ module Sequel
|
|
206
208
|
@cti_table_columns = columns
|
207
209
|
@cti_table_map = opts[:table_map] || {}
|
208
210
|
@cti_alias = opts[:alias] || @dataset.first_source
|
211
|
+
@cti_ignore_subclass_columns = opts[:ignore_subclass_columns] || []
|
209
212
|
end
|
210
213
|
end
|
211
214
|
|
@@ -232,17 +235,22 @@ module Sequel
|
|
232
235
|
# the implicit naming is incorrect.
|
233
236
|
attr_reader :cti_table_map
|
234
237
|
|
238
|
+
# An array of columns that may be duplicated in sub-classes. The
|
239
|
+
# primary key column is always allowed to be duplicated
|
240
|
+
attr_reader :cti_ignore_subclass_columns
|
241
|
+
|
235
242
|
# Freeze CTI information when freezing model class.
|
236
243
|
def freeze
|
237
244
|
@cti_models.freeze
|
238
245
|
@cti_tables.freeze
|
239
246
|
@cti_table_columns.freeze
|
240
247
|
@cti_table_map.freeze
|
248
|
+
@cti_ignore_subclass_columns.freeze
|
241
249
|
|
242
250
|
super
|
243
251
|
end
|
244
252
|
|
245
|
-
Plugins.inherited_instance_variables(self, :@cti_models=>nil, :@cti_tables=>nil, :@cti_table_columns=>nil, :@cti_instance_dataset=>nil, :@cti_table_map=>nil, :@cti_alias=>nil)
|
253
|
+
Plugins.inherited_instance_variables(self, :@cti_models=>nil, :@cti_tables=>nil, :@cti_table_columns=>nil, :@cti_instance_dataset=>nil, :@cti_table_map=>nil, :@cti_alias=>nil, :@cti_ignore_subclass_columns=>nil)
|
246
254
|
|
247
255
|
def inherited(subclass)
|
248
256
|
ds = sti_dataset
|
@@ -273,10 +281,10 @@ module Sequel
|
|
273
281
|
if cti_tables.length == 1
|
274
282
|
ds = ds.select(*self.columns.map{|cc| Sequel.qualify(cti_table_name, Sequel.identifier(cc))})
|
275
283
|
end
|
276
|
-
cols = columns - [pk]
|
284
|
+
cols = (columns - [pk]) - cti_ignore_subclass_columns
|
277
285
|
dup_cols = cols & ds.columns
|
278
286
|
unless dup_cols.empty?
|
279
|
-
raise Error, "class_table_inheritance with duplicate column names (other than the primary key column) is not supported, make sure tables have unique column names"
|
287
|
+
raise Error, "class_table_inheritance with duplicate column names (other than the primary key column) is not supported, make sure tables have unique column names (duplicate columns: #{dup_cols}). If this is desired, specify these columns in the :ignore_subclass_columns option when initializing the plugin"
|
280
288
|
end
|
281
289
|
sel_app = cols.map{|cc| Sequel.qualify(table, Sequel.identifier(cc))}
|
282
290
|
@sti_dataset = ds = ds.join(table, pk=>pk).select_append(*sel_app)
|
@@ -406,7 +406,7 @@ module Sequel
|
|
406
406
|
end
|
407
407
|
end
|
408
408
|
|
409
|
-
res = if row_proc
|
409
|
+
res = if row_proc || @opts[:eager_graph]
|
410
410
|
array = if opts[:array]
|
411
411
|
opts = opts.dup
|
412
412
|
opts.delete(:array)
|
@@ -414,7 +414,7 @@ module Sequel
|
|
414
414
|
all
|
415
415
|
end
|
416
416
|
array.map{|obj| Literal.new(Sequel.object_to_json(obj, opts, &opts[:instance_block]))}
|
417
|
-
|
417
|
+
else
|
418
418
|
all
|
419
419
|
end
|
420
420
|
|
@@ -389,7 +389,7 @@ module Sequel
|
|
389
389
|
# as well as the :array_root_name option for specifying the name of
|
390
390
|
# the root node that contains the nodes for all of the instances.
|
391
391
|
def to_xml(opts=OPTS)
|
392
|
-
raise(Sequel::Error, "Dataset#to_xml") unless row_proc
|
392
|
+
raise(Sequel::Error, "Dataset#to_xml") unless row_proc || @opts[:eager_graph]
|
393
393
|
x = model.xml_builder(opts)
|
394
394
|
name_proc = model.xml_serialize_name_proc(opts)
|
395
395
|
array = if opts[:array]
|
data/lib/sequel/version.rb
CHANGED
@@ -5,7 +5,7 @@ module Sequel
|
|
5
5
|
MAJOR = 5
|
6
6
|
# The minor version of Sequel. Bumped for every non-patch level
|
7
7
|
# release, generally around once a month.
|
8
|
-
MINOR =
|
8
|
+
MINOR = 4
|
9
9
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
10
10
|
# releases that fix regressions from previous versions.
|
11
11
|
TINY = 0
|
@@ -511,7 +511,7 @@ describe "A PostgreSQL dataset" do
|
|
511
511
|
|
512
512
|
it "should support :using when altering a column's type" do
|
513
513
|
@db.create_table!(:atest){Integer :t}
|
514
|
-
@db[:atest].insert(
|
514
|
+
@db[:atest].insert(1262404000)
|
515
515
|
@db.alter_table(:atest){set_column_type :t, Time, :using=>Sequel.cast('epoch', Time) + Sequel.cast('1 second', :interval) * :t}
|
516
516
|
@db[:atest].get(Sequel.extract(:year, :t)).must_equal 2010
|
517
517
|
end
|
@@ -32,6 +32,9 @@ end
|
|
32
32
|
IDENTIFIER_MANGLING = !!ENV['SEQUEL_IDENTIFIER_MANGLING'] unless defined?(IDENTIFIER_MANGLING)
|
33
33
|
DB.extension(:identifier_mangling) if IDENTIFIER_MANGLING
|
34
34
|
|
35
|
+
DB.extension(:pg_timestamptz) if ENV['SEQUEL_PG_TIMESTAMPTZ']
|
36
|
+
DB.extension :index_caching if ENV['SEQUEL_INDEX_CACHING']
|
37
|
+
|
35
38
|
if dch = ENV['SEQUEL_DUPLICATE_COLUMNS_HANDLER']
|
36
39
|
DB.extension :duplicate_columns_handler
|
37
40
|
DB.opts[:on_duplicate_columns] = dch.to_sym unless dch.empty?
|
@@ -554,7 +554,7 @@ describe "A SQLite database" do
|
|
554
554
|
@db.add_index :test3, :b
|
555
555
|
@db.add_index :test3, [:b, :a]
|
556
556
|
@db.drop_column :test3, :b
|
557
|
-
@db.indexes(:test3).must_equal(:
|
557
|
+
@db.indexes(:test3)[:test3_a_index].must_equal(:unique=>false, :columns=>[:a])
|
558
558
|
end
|
559
559
|
|
560
560
|
it "should have support for various #transaction modes" do
|
data/spec/bin_spec.rb
CHANGED
@@ -183,6 +183,15 @@ END
|
|
183
183
|
Marshal.load(File.read(TMP_FILE)).must_equal("`a`"=>[[:a, {:type=>:integer, :db_type=>"integer", :ruby_default=>nil, :allow_null=>true, :default=>nil, :primary_key=>false}]])
|
184
184
|
end
|
185
185
|
|
186
|
+
it "-X should dump the index cache" do
|
187
|
+
bin(:args=>"-X #{TMP_FILE}").must_equal ''
|
188
|
+
Marshal.load(File.read(TMP_FILE)).must_equal({})
|
189
|
+
DB.create_table(:a){Integer :id}
|
190
|
+
DB.create_table(:b){Integer :b, index: {name: "idx_test", unique: true}}
|
191
|
+
bin(:args=>"-X #{TMP_FILE}").must_equal ''
|
192
|
+
Marshal.load(File.read(TMP_FILE)).must_equal("`a`"=>{}, "`b`"=>{:idx_test=>{:unique=>true, :columns=>[:b]}})
|
193
|
+
end
|
194
|
+
|
186
195
|
it "-t should output full backtraces on error" do
|
187
196
|
bin(:args=>'-c "lambda{lambda{lambda{raise \'foo\'}.call}.call}.call"', :stderr=>true).count("\n").must_be :<, 3
|
188
197
|
bin(:args=>'-t -c "lambda{lambda{lambda{raise \'foo\'}.call}.call}.call"', :stderr=>true).count("\n").must_be :>, 3
|
@@ -510,7 +510,7 @@ ThreadedConnectionPoolSpecs = shared_description do
|
|
510
510
|
@pool.hold{|cc| cc.must_equal c}
|
511
511
|
@pool.hold do |cc|
|
512
512
|
cc.must_equal c
|
513
|
-
Thread.new{@pool.hold{|cc2| cc2.must_equal c2}}
|
513
|
+
Thread.new{@pool.hold{|cc2| _(cc2).must_equal c2}}.join
|
514
514
|
end
|
515
515
|
end
|
516
516
|
|
@@ -523,7 +523,7 @@ ThreadedConnectionPoolSpecs = shared_description do
|
|
523
523
|
@pool.hold{|cc| cc.must_equal c}
|
524
524
|
@pool.hold do |cc|
|
525
525
|
cc.must_equal c2
|
526
|
-
Thread.new{@pool.hold{|cc2| cc2.must_equal c}}
|
526
|
+
Thread.new{@pool.hold{|cc2| _(cc2).must_equal c}}.join
|
527
527
|
end
|
528
528
|
end
|
529
529
|
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -378,14 +378,9 @@ describe "Dataset#where" do
|
|
378
378
|
@dataset.where(nil).sql.must_equal "SELECT * FROM test WHERE NULL"
|
379
379
|
end
|
380
380
|
|
381
|
-
deprecated "should handle nil block result has no existing filter" do
|
382
|
-
@dataset.where{nil}.sql.must_equal "SELECT * FROM test"
|
383
|
-
end
|
384
|
-
|
385
|
-
# SEQUEL54
|
386
381
|
it "should handle nil block result has no existing filter" do
|
387
382
|
@dataset.where{nil}.sql.must_equal "SELECT * FROM test WHERE NULL"
|
388
|
-
end
|
383
|
+
end
|
389
384
|
|
390
385
|
it "should just clone if given an empty array or hash argument" do
|
391
386
|
@dataset.where({}).sql.must_equal @dataset.sql
|
@@ -6,7 +6,7 @@ describe "class_table_inheritance plugin" do
|
|
6
6
|
def @db.supports_schema_parsing?() true end
|
7
7
|
def @db.schema(table, opts={})
|
8
8
|
{:employees=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
|
9
|
-
:managers=>[[:id, {:type=>:integer}], [:num_staff, {:type=>:integer}]],
|
9
|
+
:managers=>[[:id, {:type=>:integer}], [:num_staff, {:type=>:integer}] ],
|
10
10
|
:executives=>[[:id, {:type=>:integer}], [:num_managers, {:type=>:integer}]],
|
11
11
|
:staff=>[[:id, {:type=>:integer}], [:manager_id, {:type=>:integer}]],
|
12
12
|
}[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
|
@@ -486,7 +486,7 @@ describe "class_table_inheritance plugin without sti_key with :alias option" do
|
|
486
486
|
end
|
487
487
|
|
488
488
|
describe "class_table_inheritance plugin with duplicate columns" do
|
489
|
-
it "should raise error" do
|
489
|
+
it "should raise error if no columns are explicitly ignored" do
|
490
490
|
@db = Sequel.mock(:autoid=>proc{|sql| 1})
|
491
491
|
def @db.supports_schema_parsing?() true end
|
492
492
|
def @db.schema(table, opts={})
|
@@ -510,6 +510,56 @@ describe "class_table_inheritance plugin with duplicate columns" do
|
|
510
510
|
end
|
511
511
|
proc{class ::Manager < Employee; end}.must_raise Sequel::Error
|
512
512
|
end
|
513
|
+
|
514
|
+
describe "with certain sub-class columns ignored" do
|
515
|
+
before do
|
516
|
+
@db = Sequel.mock(:autoid=>proc{|sql| 1})
|
517
|
+
def @db.supports_schema_parsing?() true end
|
518
|
+
def @db.schema(table, opts={})
|
519
|
+
{:employees=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}], [:updated_at, {:type=>:datetime}]],
|
520
|
+
:managers=>[[:id, {:type=>:integer}], [:num_staff, {:type=>:integer}], [:updated_at, {:type=>:datetime}], [:another_duplicate_column, {:type=>:integer}]],
|
521
|
+
:executives=>[[:id, {:type=>:integer}], [:num_managers, {:type=>:integer}], [:updated_at, {:type=>:datetime}], [:another_duplicate_column, {:type=>:integer}]],
|
522
|
+
}[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
|
523
|
+
end
|
524
|
+
@db.extend_datasets do
|
525
|
+
def columns
|
526
|
+
{[:employees]=>[:id, :name, :kind, :updated_at],
|
527
|
+
[:managers]=>[:id, :num_staff, :updated_at, :another_duplicate_column],
|
528
|
+
[:executives]=>[:id, :num_managers, :updated_at, :another_duplicate_column],
|
529
|
+
[:employees, :managers]=>[:id, :name, :kind, :updated_at, :num_staff],
|
530
|
+
}[opts[:from] + (opts[:join] || []).map{|x| x.table}]
|
531
|
+
end
|
532
|
+
end
|
533
|
+
class ::Employee < Sequel::Model(@db)
|
534
|
+
def _save_refresh; @values[:id] = 1 end
|
535
|
+
def self.columns
|
536
|
+
dataset.columns || dataset.opts[:from].first.expression.columns
|
537
|
+
end
|
538
|
+
plugin :class_table_inheritance, :ignore_subclass_columns=>[:updated_at]
|
539
|
+
end
|
540
|
+
class ::Manager < Employee
|
541
|
+
Manager.cti_ignore_subclass_columns.push(:another_duplicate_column)
|
542
|
+
end
|
543
|
+
class ::Executive < Manager; end
|
544
|
+
end
|
545
|
+
|
546
|
+
it "should not use the ignored column in a sub-class subquery" do
|
547
|
+
Employee.dataset.sql.must_equal 'SELECT * FROM employees'
|
548
|
+
Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, employees.updated_at, managers.num_staff, managers.another_duplicate_column FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees'
|
549
|
+
Executive.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, employees.updated_at, managers.num_staff, managers.another_duplicate_column, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)) AS employees'
|
550
|
+
end
|
551
|
+
|
552
|
+
it "should include schema for columns for tables for ancestor classes" do
|
553
|
+
Employee.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string}, :updated_at=>{:type=>:datetime})
|
554
|
+
Manager.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string}, :updated_at=>{:type=>:datetime}, :num_staff=>{:type=>:integer}, :another_duplicate_column=>{:type=>:integer})
|
555
|
+
Executive.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string}, :updated_at=>{:type=>:datetime}, :num_staff=>{:type=>:integer}, :another_duplicate_column=>{:type=>:integer}, :num_managers=>{:type=>:integer})
|
556
|
+
end
|
557
|
+
|
558
|
+
after do
|
559
|
+
Object.send(:remove_const, :Executive)
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
513
563
|
after do
|
514
564
|
Object.send(:remove_const, :Manager)
|
515
565
|
Object.send(:remove_const, :Employee)
|