sequel 5.2.0 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +32 -0
  3. data/bin/sequel +5 -6
  4. data/doc/release_notes/5.3.0.txt +121 -0
  5. data/doc/schema_modification.rdoc +15 -4
  6. data/doc/testing.rdoc +1 -0
  7. data/lib/sequel/adapters/jdbc.rb +4 -0
  8. data/lib/sequel/adapters/jdbc/postgresql.rb +15 -0
  9. data/lib/sequel/adapters/oracle.rb +2 -1
  10. data/lib/sequel/adapters/postgres.rb +4 -0
  11. data/lib/sequel/adapters/shared/mysql.rb +38 -3
  12. data/lib/sequel/adapters/shared/postgres.rb +15 -6
  13. data/lib/sequel/adapters/shared/sqlite.rb +10 -0
  14. data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -0
  15. data/lib/sequel/connection_pool.rb +12 -0
  16. data/lib/sequel/database/misc.rb +13 -0
  17. data/lib/sequel/dataset/dataset_module.rb +1 -1
  18. data/lib/sequel/dataset/features.rb +5 -0
  19. data/lib/sequel/dataset/query.rb +20 -6
  20. data/lib/sequel/dataset/sql.rb +3 -0
  21. data/lib/sequel/extensions/pg_extended_date_support.rb +15 -0
  22. data/lib/sequel/extensions/synchronize_sql.rb +45 -0
  23. data/lib/sequel/model/associations.rb +1 -0
  24. data/lib/sequel/model/base.rb +4 -11
  25. data/lib/sequel/plugins/validation_helpers.rb +2 -2
  26. data/lib/sequel/version.rb +1 -1
  27. data/spec/adapters/postgres_spec.rb +5 -34
  28. data/spec/core/database_spec.rb +32 -0
  29. data/spec/core/dataset_spec.rb +19 -0
  30. data/spec/core/mock_adapter_spec.rb +65 -0
  31. data/spec/extensions/association_pks_spec.rb +26 -33
  32. data/spec/extensions/class_table_inheritance_spec.rb +18 -32
  33. data/spec/extensions/composition_spec.rb +7 -23
  34. data/spec/extensions/list_spec.rb +4 -5
  35. data/spec/extensions/many_through_many_spec.rb +24 -32
  36. data/spec/extensions/optimistic_locking_spec.rb +1 -1
  37. data/spec/extensions/pg_array_associations_spec.rb +18 -25
  38. data/spec/extensions/pg_extended_date_support_spec.rb +13 -0
  39. data/spec/extensions/pg_hstore_spec.rb +2 -2
  40. data/spec/extensions/prepared_statements_safe_spec.rb +6 -6
  41. data/spec/extensions/pretty_table_spec.rb +39 -8
  42. data/spec/extensions/rcte_tree_spec.rb +22 -33
  43. data/spec/extensions/schema_dumper_spec.rb +42 -31
  44. data/spec/extensions/serialization_spec.rb +3 -3
  45. data/spec/extensions/synchronize_sql_spec.rb +124 -0
  46. data/spec/extensions/timestamps_spec.rb +2 -4
  47. data/spec/extensions/update_or_create_spec.rb +11 -15
  48. data/spec/extensions/uuid_spec.rb +2 -3
  49. data/spec/extensions/xml_serializer_spec.rb +5 -10
  50. data/spec/integration/database_test.rb +1 -1
  51. data/spec/integration/dataset_test.rb +7 -0
  52. data/spec/integration/plugin_test.rb +1 -1
  53. data/spec/integration/schema_test.rb +3 -3
  54. data/spec/integration/spec_helper.rb +4 -0
  55. data/spec/model/base_spec.rb +6 -0
  56. data/spec/model/eager_loading_spec.rb +31 -6
  57. data/spec/model/model_spec.rb +9 -19
  58. data/spec/model/record_spec.rb +4 -8
  59. 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
@@ -56,6 +56,8 @@ module Sequel
56
56
  UniqueConstraintViolation
57
57
  when 1451, 1452
58
58
  ForeignKeyConstraintViolation
59
+ when 4025
60
+ CheckConstraintViolation
59
61
  else
60
62
  super
61
63
  end
@@ -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
@@ -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
@@ -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 hoist_cte?(dataset)
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
- if expr
1217
- return SQL::BooleanExpression.new(:AND, filter_expr(expr), filter_expr(Sequel.virtual_row(&block)))
1218
- else
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
@@ -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
@@ -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
- end
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
@@ -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 = 2
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
- args = ['tf', 'SELECT 1', {:returns=>:integer}]
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}]