sequel 5.2.0 → 5.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +32 -0
- data/bin/sequel +5 -6
- data/doc/release_notes/5.3.0.txt +121 -0
- data/doc/schema_modification.rdoc +15 -4
- data/doc/testing.rdoc +1 -0
- data/lib/sequel/adapters/jdbc.rb +4 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +15 -0
- data/lib/sequel/adapters/oracle.rb +2 -1
- data/lib/sequel/adapters/postgres.rb +4 -0
- data/lib/sequel/adapters/shared/mysql.rb +38 -3
- data/lib/sequel/adapters/shared/postgres.rb +15 -6
- data/lib/sequel/adapters/shared/sqlite.rb +10 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -0
- data/lib/sequel/connection_pool.rb +12 -0
- data/lib/sequel/database/misc.rb +13 -0
- data/lib/sequel/dataset/dataset_module.rb +1 -1
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/query.rb +20 -6
- data/lib/sequel/dataset/sql.rb +3 -0
- data/lib/sequel/extensions/pg_extended_date_support.rb +15 -0
- data/lib/sequel/extensions/synchronize_sql.rb +45 -0
- data/lib/sequel/model/associations.rb +1 -0
- data/lib/sequel/model/base.rb +4 -11
- data/lib/sequel/plugins/validation_helpers.rb +2 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +5 -34
- data/spec/core/database_spec.rb +32 -0
- data/spec/core/dataset_spec.rb +19 -0
- data/spec/core/mock_adapter_spec.rb +65 -0
- data/spec/extensions/association_pks_spec.rb +26 -33
- data/spec/extensions/class_table_inheritance_spec.rb +18 -32
- data/spec/extensions/composition_spec.rb +7 -23
- data/spec/extensions/list_spec.rb +4 -5
- data/spec/extensions/many_through_many_spec.rb +24 -32
- data/spec/extensions/optimistic_locking_spec.rb +1 -1
- data/spec/extensions/pg_array_associations_spec.rb +18 -25
- data/spec/extensions/pg_extended_date_support_spec.rb +13 -0
- data/spec/extensions/pg_hstore_spec.rb +2 -2
- data/spec/extensions/prepared_statements_safe_spec.rb +6 -6
- data/spec/extensions/pretty_table_spec.rb +39 -8
- data/spec/extensions/rcte_tree_spec.rb +22 -33
- data/spec/extensions/schema_dumper_spec.rb +42 -31
- data/spec/extensions/serialization_spec.rb +3 -3
- data/spec/extensions/synchronize_sql_spec.rb +124 -0
- data/spec/extensions/timestamps_spec.rb +2 -4
- data/spec/extensions/update_or_create_spec.rb +11 -15
- data/spec/extensions/uuid_spec.rb +2 -3
- data/spec/extensions/xml_serializer_spec.rb +5 -10
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +7 -0
- data/spec/integration/plugin_test.rb +1 -1
- data/spec/integration/schema_test.rb +3 -3
- data/spec/integration/spec_helper.rb +4 -0
- data/spec/model/base_spec.rb +6 -0
- data/spec/model/eager_loading_spec.rb +31 -6
- data/spec/model/model_spec.rb +9 -19
- data/spec/model/record_spec.rb +4 -8
- metadata +6 -2
@@ -641,6 +641,11 @@ module Sequel
|
|
641
641
|
db.sqlite_version >= 30803
|
642
642
|
end
|
643
643
|
|
644
|
+
# SQLite supports CTEs in subqueries if it supports CTEs.
|
645
|
+
def supports_cte_in_subqueries?
|
646
|
+
supports_cte?
|
647
|
+
end
|
648
|
+
|
644
649
|
# SQLite does not support table aliases with column aliases
|
645
650
|
def supports_derived_column_lists?
|
646
651
|
false
|
@@ -765,6 +770,11 @@ module Sequel
|
|
765
770
|
expression_list_append(sql, opts[:values])
|
766
771
|
end
|
767
772
|
|
773
|
+
# SQLite does not support CTEs directly inside UNION/INTERSECT/EXCEPT.
|
774
|
+
def supports_cte_in_compounds?
|
775
|
+
false
|
776
|
+
end
|
777
|
+
|
768
778
|
# SQLite supports quoted function names.
|
769
779
|
def supports_quoted_function_names?
|
770
780
|
true
|
@@ -76,6 +76,9 @@ class Sequel::ConnectionPool
|
|
76
76
|
# connection made, and is usually used to set custom per-connection settings.
|
77
77
|
attr_accessor :after_connect
|
78
78
|
|
79
|
+
# An array of sql strings to execute on each new connection.
|
80
|
+
attr_accessor :connect_sqls
|
81
|
+
|
79
82
|
# The Sequel::Database object tied to this connection pool.
|
80
83
|
attr_accessor :db
|
81
84
|
|
@@ -86,6 +89,7 @@ class Sequel::ConnectionPool
|
|
86
89
|
# :after_connect :: A callable object called after each new connection is made, with the
|
87
90
|
# connection object (and server argument if the callable accepts 2 arguments),
|
88
91
|
# useful for customizations that you want to apply to all connections.
|
92
|
+
# :connect_sqls :: An array of sql strings to execute on each new connection, after :after_connect runs.
|
89
93
|
# :preconnect :: Automatically create the maximum number of connections, so that they don't
|
90
94
|
# need to be created as needed. This is useful when connecting takes a long time
|
91
95
|
# and you want to avoid possible latency during runtime.
|
@@ -94,6 +98,7 @@ class Sequel::ConnectionPool
|
|
94
98
|
def initialize(db, opts=OPTS)
|
95
99
|
@db = db
|
96
100
|
@after_connect = opts[:after_connect]
|
101
|
+
@connect_sqls = opts[:connect_sqls]
|
97
102
|
@error_classes = db.send(:database_error_classes).dup.freeze
|
98
103
|
end
|
99
104
|
|
@@ -119,6 +124,7 @@ class Sequel::ConnectionPool
|
|
119
124
|
def make_new(server)
|
120
125
|
begin
|
121
126
|
conn = @db.connect(server)
|
127
|
+
|
122
128
|
if ac = @after_connect
|
123
129
|
if ac.arity == 2
|
124
130
|
ac.call(conn, server)
|
@@ -126,6 +132,12 @@ class Sequel::ConnectionPool
|
|
126
132
|
ac.call(conn)
|
127
133
|
end
|
128
134
|
end
|
135
|
+
|
136
|
+
if cs = @connect_sqls
|
137
|
+
cs.each do |sql|
|
138
|
+
db.send(:log_connection_execute, conn, sql)
|
139
|
+
end
|
140
|
+
end
|
129
141
|
rescue Exception=>exception
|
130
142
|
raise Sequel.convert_exception_class(exception, Sequel::DatabaseConnectionError)
|
131
143
|
end
|
data/lib/sequel/database/misc.rb
CHANGED
@@ -151,6 +151,19 @@ module Sequel
|
|
151
151
|
concurrent = typecast_value_string(@opts[:preconnect]) == "concurrently"
|
152
152
|
@pool.send(:preconnect, concurrent)
|
153
153
|
end
|
154
|
+
|
155
|
+
case exts = @opts[:extensions]
|
156
|
+
when String
|
157
|
+
extension(*exts.split(',').map(&:to_sym))
|
158
|
+
when Array
|
159
|
+
extension(*exts)
|
160
|
+
when Symbol
|
161
|
+
extension(exts)
|
162
|
+
when nil
|
163
|
+
# nothing
|
164
|
+
else
|
165
|
+
raise Error, "unsupported Database :extensions option: #{@opts[:extensions].inspect}"
|
166
|
+
end
|
154
167
|
end
|
155
168
|
|
156
169
|
# Freeze internal data structures for the Database instance.
|
@@ -20,7 +20,7 @@ module Sequel
|
|
20
20
|
meths = (<<-METHS).split.map(&:to_sym)
|
21
21
|
where exclude exclude_having having
|
22
22
|
distinct grep group group_and_count group_append
|
23
|
-
limit offset order order_append order_prepend
|
23
|
+
limit offset order order_append order_prepend reverse
|
24
24
|
select select_all select_append select_group server
|
25
25
|
METHS
|
26
26
|
|
@@ -192,6 +192,11 @@ module Sequel
|
|
192
192
|
true
|
193
193
|
end
|
194
194
|
|
195
|
+
# Whether common table expressions are supported in UNION/INTERSECT/EXCEPT clauses.
|
196
|
+
def supports_cte_in_compounds?
|
197
|
+
supports_cte_in_subqueries?
|
198
|
+
end
|
199
|
+
|
195
200
|
# Whether the database supports quoting function names.
|
196
201
|
def supports_quoted_function_names?
|
197
202
|
false
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -774,6 +774,16 @@ module Sequel
|
|
774
774
|
# DB[:items].returning # RETURNING *
|
775
775
|
# DB[:items].returning(nil) # RETURNING NULL
|
776
776
|
# DB[:items].returning(:id, :name) # RETURNING id, name
|
777
|
+
#
|
778
|
+
# DB[:items].returning.insert(:a=>1) do |hash|
|
779
|
+
# # hash for each row inserted, with values for all columns
|
780
|
+
# end
|
781
|
+
# DB[:items].returning.update(:a=>1) do |hash|
|
782
|
+
# # hash for each row updated, with values for all columns
|
783
|
+
# end
|
784
|
+
# DB[:items].returning.delete(:a=>1) do |hash|
|
785
|
+
# # hash for each row deleted, with values for all columns
|
786
|
+
# end
|
777
787
|
def returning(*values)
|
778
788
|
raise Error, "RETURNING is not supported on #{db.database_type}" unless supports_returning?(:insert)
|
779
789
|
clone(:returning=>values.freeze)
|
@@ -1122,7 +1132,7 @@ module Sequel
|
|
1122
1132
|
|
1123
1133
|
# Add the dataset to the list of compounds
|
1124
1134
|
def compound_clone(type, dataset, opts)
|
1125
|
-
if
|
1135
|
+
if dataset.is_a?(Dataset) && dataset.opts[:with] && !supports_cte_in_compounds?
|
1126
1136
|
s, ds = hoist_cte(dataset)
|
1127
1137
|
return s.compound_clone(type, ds, opts)
|
1128
1138
|
end
|
@@ -1199,6 +1209,12 @@ module Sequel
|
|
1199
1209
|
cond = filter_expr(cond, &block)
|
1200
1210
|
cond = SQL::BooleanExpression.invert(cond) if invert
|
1201
1211
|
cond = SQL::BooleanExpression.new(combine, @opts[clause], cond) if @opts[clause]
|
1212
|
+
|
1213
|
+
if cond.nil?
|
1214
|
+
Sequel::Deprecation.deprecate('Filtering method called on dataset with no existing filter with virtual row block and no argument and virtual row block returned nil. Currently, this results in the filter being ignored instead of using a NULL filter. In Sequel 5.4+, this behavior will change to using a NULL filter, similar to the behavior in all other cases.')
|
1215
|
+
#cond = Sequel::NULL
|
1216
|
+
end
|
1217
|
+
|
1202
1218
|
clone(clause => cond)
|
1203
1219
|
end
|
1204
1220
|
end
|
@@ -1213,11 +1229,9 @@ module Sequel
|
|
1213
1229
|
expr = nil if expr == EMPTY_ARRAY
|
1214
1230
|
|
1215
1231
|
if block
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
return filter_expr(Sequel.virtual_row(&block))
|
1220
|
-
end
|
1232
|
+
cond = filter_expr(Sequel.virtual_row(&block))
|
1233
|
+
cond = SQL::BooleanExpression.new(:AND, filter_expr(expr), cond) if expr
|
1234
|
+
return cond
|
1221
1235
|
end
|
1222
1236
|
|
1223
1237
|
case expr
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -49,6 +49,9 @@ module Sequel
|
|
49
49
|
|
50
50
|
if values.is_a?(Array) && values.empty? && !insert_supports_empty_values?
|
51
51
|
columns, values = insert_empty_columns_values
|
52
|
+
elsif values.is_a?(Dataset) && hoist_cte?(values) && supports_cte?(:insert)
|
53
|
+
ds, values = hoist_cte(values)
|
54
|
+
return ds.clone(:columns=>columns, :values=>values).send(:_insert_sql)
|
52
55
|
end
|
53
56
|
clone(:columns=>columns, :values=>values).send(:_insert_sql)
|
54
57
|
end
|
@@ -27,6 +27,8 @@ module Sequel
|
|
27
27
|
TIME_YEAR_1 = Time.at(-62135596800).utc
|
28
28
|
INFINITE_TIMESTAMP_STRINGS = ['infinity'.freeze, '-infinity'.freeze].freeze
|
29
29
|
INFINITE_DATETIME_VALUES = ([PLUS_INFINITY, MINUS_INFINITY] + INFINITE_TIMESTAMP_STRINGS).freeze
|
30
|
+
PLUS_DATE_INFINITY = Date::Infinity.new
|
31
|
+
MINUS_DATE_INFINITY = -PLUS_DATE_INFINITY
|
30
32
|
|
31
33
|
# Add dataset methods and update the conversion proces for dates and timestamps.
|
32
34
|
def self.extended(db)
|
@@ -52,6 +54,8 @@ module Sequel
|
|
52
54
|
:nil
|
53
55
|
when 'string'
|
54
56
|
:string
|
57
|
+
when 'date'
|
58
|
+
:date
|
55
59
|
when 'float'
|
56
60
|
:float
|
57
61
|
when String, true
|
@@ -111,6 +115,8 @@ module Sequel
|
|
111
115
|
nil
|
112
116
|
when :string
|
113
117
|
value
|
118
|
+
when :date
|
119
|
+
value == 'infinity' ? PLUS_DATE_INFINITY : MINUS_DATE_INFINITY
|
114
120
|
else
|
115
121
|
value == 'infinity' ? PLUS_INFINITY : MINUS_INFINITY
|
116
122
|
end
|
@@ -173,6 +179,15 @@ module Sequel
|
|
173
179
|
end
|
174
180
|
end
|
175
181
|
|
182
|
+
# Handle Date::Infinity values
|
183
|
+
def literal_other_append(sql, v)
|
184
|
+
if v.is_a?(Date::Infinity)
|
185
|
+
sql << (v > 0 ? "'infinity'" : "'-infinity'")
|
186
|
+
else
|
187
|
+
super
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
176
191
|
if RUBY_ENGINE == 'jruby'
|
177
192
|
# :nocov:
|
178
193
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The synchronize_sql extension checks out a connection from the pool while
|
4
|
+
# generating an SQL string. In cases where a connection is necessary
|
5
|
+
# in order to properly escape input, and multiple inputs in the query need
|
6
|
+
# escaping, this can result in fewer connection checkouts and better
|
7
|
+
# overall performance. In other cases this results in a performance decrease
|
8
|
+
# because a connection is checked out and either not used or kept checked out
|
9
|
+
# longer than necessary.
|
10
|
+
#
|
11
|
+
# The adapters where this extension may improve performance include amalgalite,
|
12
|
+
# mysql2, postgres, jdbc/postgresql, and tinytds. In these adapters, escaping
|
13
|
+
# strings requires a connection object for as proper escaping requires calling
|
14
|
+
# an escaping method on the connection object.
|
15
|
+
#
|
16
|
+
# This extension is most helpful when dealing with queries with lots of
|
17
|
+
# strings that need escaping (e.g. IN queries with long lists). By default,
|
18
|
+
# a connection will be checked out and back in for each string to be escaped,
|
19
|
+
# which under high contention can cause the query to spend longer generating
|
20
|
+
# the SQL string than the actual pool timeout (since every individual checkout
|
21
|
+
# will take less than the timeout, but the sum of all of them can be greater).
|
22
|
+
#
|
23
|
+
# This extension is unnecessary and will decrease performance if the single
|
24
|
+
# threaded connection pool is used.
|
25
|
+
|
26
|
+
#
|
27
|
+
module Sequel
|
28
|
+
class Dataset
|
29
|
+
module SynchronizeSQL
|
30
|
+
%w'insert select update delete'.each do |type|
|
31
|
+
define_method(:"#{type}_sql") do |*args|
|
32
|
+
if @opts[:sql].is_a?(String)
|
33
|
+
return super(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
db.synchronize(@opts[:server]) do
|
37
|
+
super(*args)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
register_extension(:synchronize_sql, SynchronizeSQL)
|
44
|
+
end
|
45
|
+
end
|
@@ -145,6 +145,7 @@ module Sequel
|
|
145
145
|
rn = ds.row_number_column
|
146
146
|
limit, offset = limit_and_offset
|
147
147
|
ds = ds.unordered.select_append{|o| o.row_number.function.over(:partition=>predicate_key, :order=>ds.opts[:order]).as(rn)}.from_self
|
148
|
+
ds = ds.order(rn) if ds.db.database_type == :mysql
|
148
149
|
ds = if !returns_array?
|
149
150
|
ds.where(rn => offset ? offset+1 : 1)
|
150
151
|
elsif offset
|
data/lib/sequel/model/base.rb
CHANGED
@@ -1498,22 +1498,15 @@ module Sequel
|
|
1498
1498
|
model.default_set_fields_options
|
1499
1499
|
end
|
1500
1500
|
|
1501
|
-
case opts[:missing]
|
1502
|
-
when :skip
|
1501
|
+
case missing = opts[:missing]
|
1502
|
+
when :skip, :raise
|
1503
|
+
do_raise = true if missing == :raise
|
1503
1504
|
fields.each do |f|
|
1504
1505
|
if hash.has_key?(f)
|
1505
1506
|
set_column_value("#{f}=", hash[f])
|
1506
1507
|
elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
|
1507
1508
|
set_column_value("#{sf}=", hash[sf])
|
1508
|
-
|
1509
|
-
end
|
1510
|
-
when :raise
|
1511
|
-
fields.each do |f|
|
1512
|
-
if hash.has_key?(f)
|
1513
|
-
set_column_value("#{f}=", hash[f])
|
1514
|
-
elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
|
1515
|
-
set_column_value("#{sf}=", hash[sf])
|
1516
|
-
else
|
1509
|
+
elsif do_raise
|
1517
1510
|
raise(Sequel::Error, "missing field in hash: #{f.inspect} not in #{hash.inspect}")
|
1518
1511
|
end
|
1519
1512
|
end
|
@@ -48,9 +48,9 @@ module Sequel
|
|
48
48
|
# def default_validation_helpers_options(type)
|
49
49
|
# case type
|
50
50
|
# when :exact_length
|
51
|
-
# {message: lambda{|exact| I18n.t("errors.exact_length", exact: exact)}
|
51
|
+
# {message: lambda{|exact| I18n.t("errors.exact_length", exact: exact)}}
|
52
52
|
# when :integer
|
53
|
-
# {message: lambda{I18n.t("errors.integer")}
|
53
|
+
# {message: lambda{I18n.t("errors.integer")}}
|
54
54
|
# else
|
55
55
|
# super
|
56
56
|
# end
|
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 = 3
|
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
|
@@ -225,14 +225,6 @@ describe "PostgreSQL", 'INSERT ON CONFLICT' do
|
|
225
225
|
@ds.insert_conflict(:constraint=>:ic_test_a_uidx, :update=>{:b=>6}, :update_where=>{Sequel[:ic_test][:b]=>4}).insert(1, 3, 4).must_be_nil
|
226
226
|
@ds.all.must_equal [{:a=>1, :b=>5, :c=>5, :c_is_unique=>false}]
|
227
227
|
end
|
228
|
-
|
229
|
-
it "Dataset#insert_conflict should respect expressions in the target argument" do
|
230
|
-
@ds.insert_conflict(:target=>:a).insert_sql(1, 2, 3).must_equal "INSERT INTO \"ic_test\" VALUES (1, 2, 3) ON CONFLICT (\"a\") DO NOTHING"
|
231
|
-
@ds.insert_conflict(:target=>:c, :conflict_where=>{:c_is_unique=>true}).insert_sql(1, 2, 3).must_equal "INSERT INTO \"ic_test\" VALUES (1, 2, 3) ON CONFLICT (\"c\") WHERE (\"c_is_unique\" IS TRUE) DO NOTHING"
|
232
|
-
@ds.insert_conflict(:target=>[:b, :c]).insert_sql(1, 2, 3).must_equal "INSERT INTO \"ic_test\" VALUES (1, 2, 3) ON CONFLICT (\"b\", \"c\") DO NOTHING"
|
233
|
-
@ds.insert_conflict(:target=>[:b, Sequel.function(:round, :c)]).insert_sql(1, 2, 3).must_equal "INSERT INTO \"ic_test\" VALUES (1, 2, 3) ON CONFLICT (\"b\", round(\"c\")) DO NOTHING"
|
234
|
-
@ds.insert_conflict(:target=>[:b, Sequel.virtual_row{|o| o.round(:c)}]).insert_sql(1, 2, 3).must_equal "INSERT INTO \"ic_test\" VALUES (1, 2, 3) ON CONFLICT (\"b\", round(\"c\")) DO NOTHING"
|
235
|
-
end
|
236
228
|
end if DB.server_version >= 90500
|
237
229
|
|
238
230
|
describe "A PostgreSQL database" do
|
@@ -355,13 +347,6 @@ describe "A PostgreSQL database" do
|
|
355
347
|
Sequel.connect(DB.opts.merge(:notice_receiver=>proc{|r| a = r.result_error_message})){|db| db.do("BEGIN\nRAISE WARNING 'foo';\nEND;")}
|
356
348
|
a.must_equal "WARNING: foo\n"
|
357
349
|
end if uses_pg && DB.server_version >= 90000
|
358
|
-
|
359
|
-
# These only test the SQL created, because a true test using file_fdw or postgres_fdw
|
360
|
-
# requires superuser permissions, and you should not be running the tests as a superuser.
|
361
|
-
it "should support creating and dropping foreign tables" do
|
362
|
-
DB.send(:create_table_sql, :t, DB.create_table_generator{Integer :a}, :foreign=>:f, :options=>{:o=>1}).must_equal 'CREATE FOREIGN TABLE "t" ("a" integer) SERVER "f" OPTIONS (o \'1\')'
|
363
|
-
DB.send(:drop_table_sql, :t, :foreign=>true).must_equal 'DROP FOREIGN TABLE "t"'
|
364
|
-
end
|
365
350
|
end
|
366
351
|
|
367
352
|
describe "A PostgreSQL database with domain types" do
|
@@ -787,6 +772,8 @@ describe "A PostgreSQL dataset with a timestamp field" do
|
|
787
772
|
@db[:test3].get(:time).must_be_nil
|
788
773
|
@db.convert_infinite_timestamps = 'string'
|
789
774
|
@db[:test3].get(:time).must_equal 'infinity'
|
775
|
+
@db.convert_infinite_timestamps = 'date'
|
776
|
+
@db[:test3].get(:time).must_equal Date::Infinity.new
|
790
777
|
@db.convert_infinite_timestamps = 'float'
|
791
778
|
@db[:test3].get(:time).must_equal 1.0/0.0
|
792
779
|
@db.convert_infinite_timestamps = 't'
|
@@ -805,6 +792,8 @@ describe "A PostgreSQL dataset with a timestamp field" do
|
|
805
792
|
@db[:test3].get(:time).must_be_nil
|
806
793
|
@db.convert_infinite_timestamps = :string
|
807
794
|
@db[:test3].get(:time).must_equal '-infinity'
|
795
|
+
@db.convert_infinite_timestamps = :date
|
796
|
+
@db[:test3].get(:time).must_equal -Date::Infinity.new
|
808
797
|
@db.convert_infinite_timestamps = :float
|
809
798
|
@db[:test3].get(:time).must_equal(-1.0/0.0)
|
810
799
|
end
|
@@ -1527,47 +1516,34 @@ describe "Postgres::Database functions, languages, schemas, and triggers" do
|
|
1527
1516
|
|
1528
1517
|
it "#create_function and #drop_function should create and drop functions" do
|
1529
1518
|
proc{@d['SELECT tf()'].all}.must_raise(Sequel::DatabaseError)
|
1530
|
-
|
1531
|
-
@d.send(:create_function_sql, *args).must_match(/\A\s*CREATE FUNCTION tf\(\)\s+RETURNS integer\s+LANGUAGE SQL\s+AS 'SELECT 1'\s*\z/)
|
1532
|
-
@d.create_function(*args)
|
1519
|
+
@d.create_function('tf', 'SELECT 1', :returns=>:integer)
|
1533
1520
|
@d['SELECT tf()'].all.must_equal [{:tf=>1}]
|
1534
|
-
@d.send(:drop_function_sql, 'tf').must_equal 'DROP FUNCTION tf()'
|
1535
1521
|
@d.drop_function('tf')
|
1536
1522
|
proc{@d['SELECT tf()'].all}.must_raise(Sequel::DatabaseError)
|
1537
1523
|
end
|
1538
1524
|
|
1539
1525
|
it "#create_function and #drop_function should support options" do
|
1540
1526
|
args = ['tf', 'SELECT $1 + $2', {:args=>[[:integer, :a], :integer], :replace=>true, :returns=>:integer, :language=>'SQL', :behavior=>:immutable, :strict=>true, :security_definer=>true, :cost=>2, :set=>{:search_path => 'public'}}]
|
1541
|
-
@d.send(:create_function_sql,*args).must_match(/\A\s*CREATE OR REPLACE FUNCTION tf\(a integer, integer\)\s+RETURNS integer\s+LANGUAGE SQL\s+IMMUTABLE\s+STRICT\s+SECURITY DEFINER\s+COST 2\s+SET search_path = public\s+AS 'SELECT \$1 \+ \$2'\s*\z/)
|
1542
1527
|
@d.create_function(*args)
|
1543
1528
|
# Make sure replace works
|
1544
1529
|
@d.create_function(*args)
|
1545
1530
|
@d['SELECT tf(1, 2)'].all.must_equal [{:tf=>3}]
|
1546
1531
|
args = ['tf', {:if_exists=>true, :cascade=>true, :args=>[[:integer, :a], :integer]}]
|
1547
|
-
@d.send(:drop_function_sql,*args).must_equal 'DROP FUNCTION IF EXISTS tf(a integer, integer) CASCADE'
|
1548
1532
|
@d.drop_function(*args)
|
1549
1533
|
# Make sure if exists works
|
1550
1534
|
@d.drop_function(*args)
|
1551
1535
|
end
|
1552
1536
|
|
1553
1537
|
it "#create_language and #drop_language should create and drop languages" do
|
1554
|
-
@d.send(:create_language_sql, :plpgsql).must_equal 'CREATE LANGUAGE plpgsql'
|
1555
1538
|
@d.create_language(:plpgsql, :replace=>true) if @d.server_version < 90000
|
1556
1539
|
proc{@d.create_language(:plpgsql)}.must_raise(Sequel::DatabaseError)
|
1557
|
-
@d.send(:drop_language_sql, :plpgsql).must_equal 'DROP LANGUAGE plpgsql'
|
1558
1540
|
@d.drop_language(:plpgsql) if @d.server_version < 90000
|
1559
1541
|
proc{@d.drop_language(:plpgsql)}.must_raise(Sequel::DatabaseError) if @d.server_version < 90000
|
1560
|
-
@d.send(:create_language_sql, :plpgsql, :replace=>true, :trusted=>true, :handler=>:a, :validator=>:b).must_equal(@d.server_version >= 90000 ? 'CREATE OR REPLACE TRUSTED LANGUAGE plpgsql HANDLER a VALIDATOR b' : 'CREATE TRUSTED LANGUAGE plpgsql HANDLER a VALIDATOR b')
|
1561
|
-
@d.send(:drop_language_sql, :plpgsql, :if_exists=>true, :cascade=>true).must_equal 'DROP LANGUAGE IF EXISTS plpgsql CASCADE'
|
1562
1542
|
# Make sure if exists works
|
1563
1543
|
@d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true) if @d.server_version < 90000
|
1564
1544
|
end
|
1565
1545
|
|
1566
1546
|
it "#create_schema and #drop_schema should create and drop schemas" do
|
1567
|
-
@d.send(:create_schema_sql, :sequel).must_equal 'CREATE SCHEMA "sequel"'
|
1568
|
-
@d.send(:create_schema_sql, :sequel, :if_not_exists=>true, :owner=>:foo).must_equal 'CREATE SCHEMA IF NOT EXISTS "sequel" AUTHORIZATION "foo"'
|
1569
|
-
@d.send(:drop_schema_sql, :sequel).must_equal 'DROP SCHEMA "sequel"'
|
1570
|
-
@d.send(:drop_schema_sql, :sequel, :if_exists=>true, :cascade=>true).must_equal 'DROP SCHEMA IF EXISTS "sequel" CASCADE'
|
1571
1547
|
@d.create_schema(:sequel)
|
1572
1548
|
@d.create_schema(:sequel, :if_not_exists=>true) if @d.server_version >= 90300
|
1573
1549
|
@d.create_table(Sequel[:sequel][:test]){Integer :a}
|
@@ -1577,7 +1553,6 @@ describe "Postgres::Database functions, languages, schemas, and triggers" do
|
|
1577
1553
|
it "#create_trigger and #drop_trigger should create and drop triggers" do
|
1578
1554
|
@d.create_language(:plpgsql) if @d.server_version < 90000
|
1579
1555
|
@d.create_function(:tf, 'BEGIN IF NEW.value IS NULL THEN RAISE EXCEPTION \'Blah\'; END IF; RETURN NEW; END;', :language=>:plpgsql, :returns=>:trigger)
|
1580
|
-
@d.send(:create_trigger_sql, :test, :identity, :tf, :each_row=>true).must_equal 'CREATE TRIGGER identity BEFORE INSERT OR UPDATE OR DELETE ON "test" FOR EACH ROW EXECUTE PROCEDURE tf()'
|
1581
1556
|
@d.create_table(:test){String :name; Integer :value}
|
1582
1557
|
@d.create_trigger(:test, :identity, :tf, :each_row=>true)
|
1583
1558
|
@d[:test].insert(:name=>'a', :value=>1)
|
@@ -1586,15 +1561,11 @@ describe "Postgres::Database functions, languages, schemas, and triggers" do
|
|
1586
1561
|
@d[:test].filter(:name=>'a').all.must_equal [{:name=>'a', :value=>1}]
|
1587
1562
|
@d[:test].filter(:name=>'a').update(:value=>3)
|
1588
1563
|
@d[:test].filter(:name=>'a').all.must_equal [{:name=>'a', :value=>3}]
|
1589
|
-
@d.send(:drop_trigger_sql, :test, :identity).must_equal 'DROP TRIGGER identity ON "test"'
|
1590
1564
|
@d.drop_trigger(:test, :identity)
|
1591
|
-
@d.send(:create_trigger_sql, :test, :identity, :tf, :after=>true, :events=>:insert, :args=>[1, 'a']).must_equal 'CREATE TRIGGER identity AFTER INSERT ON "test" EXECUTE PROCEDURE tf(1, \'a\')'
|
1592
|
-
@d.send(:drop_trigger_sql, :test, :identity, :if_exists=>true, :cascade=>true).must_equal 'DROP TRIGGER IF EXISTS identity ON "test" CASCADE'
|
1593
1565
|
# Make sure if exists works
|
1594
1566
|
@d.drop_trigger(:test, :identity, :if_exists=>true, :cascade=>true)
|
1595
1567
|
|
1596
1568
|
if @d.supports_trigger_conditions?
|
1597
|
-
@d.send(:create_trigger_sql, :test, :identity, :tf, :each_row=>true, :when=> {Sequel[:new][:name] => 'b'}).must_equal %q{CREATE TRIGGER identity BEFORE INSERT OR UPDATE OR DELETE ON "test" FOR EACH ROW WHEN ("new"."name" = 'b') EXECUTE PROCEDURE tf()}
|
1598
1569
|
@d.create_trigger(:test, :identity, :tf, :each_row=>true, :events => :update, :when=> {Sequel[:new][:name] => 'b'})
|
1599
1570
|
@d[:test].filter(:name=>'a').update(:value=>nil)
|
1600
1571
|
@d[:test].filter(:name=>'a').all.must_equal [{:name=>'a', :value=>nil}]
|