sequel 3.23.0 → 3.24.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.
Files changed (76) hide show
  1. data/CHANGELOG +64 -0
  2. data/doc/association_basics.rdoc +43 -5
  3. data/doc/model_hooks.rdoc +64 -27
  4. data/doc/prepared_statements.rdoc +8 -4
  5. data/doc/reflection.rdoc +8 -2
  6. data/doc/release_notes/3.23.0.txt +1 -1
  7. data/doc/release_notes/3.24.0.txt +420 -0
  8. data/lib/sequel/adapters/db2.rb +8 -1
  9. data/lib/sequel/adapters/firebird.rb +25 -9
  10. data/lib/sequel/adapters/informix.rb +4 -19
  11. data/lib/sequel/adapters/jdbc.rb +34 -17
  12. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  13. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  14. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  15. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  16. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  17. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  18. data/lib/sequel/adapters/postgres.rb +30 -1
  19. data/lib/sequel/adapters/shared/access.rb +10 -0
  20. data/lib/sequel/adapters/shared/informix.rb +45 -0
  21. data/lib/sequel/adapters/shared/mssql.rb +82 -8
  22. data/lib/sequel/adapters/shared/mysql.rb +25 -7
  23. data/lib/sequel/adapters/shared/postgres.rb +39 -6
  24. data/lib/sequel/adapters/shared/sqlite.rb +57 -5
  25. data/lib/sequel/adapters/sqlite.rb +8 -3
  26. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  27. data/lib/sequel/ast_transformer.rb +190 -0
  28. data/lib/sequel/core.rb +1 -1
  29. data/lib/sequel/database/misc.rb +6 -0
  30. data/lib/sequel/database/query.rb +33 -3
  31. data/lib/sequel/database/schema_methods.rb +6 -2
  32. data/lib/sequel/dataset/features.rb +6 -0
  33. data/lib/sequel/dataset/prepared_statements.rb +17 -2
  34. data/lib/sequel/dataset/query.rb +17 -0
  35. data/lib/sequel/dataset/sql.rb +2 -53
  36. data/lib/sequel/exceptions.rb +4 -0
  37. data/lib/sequel/extensions/to_dot.rb +95 -83
  38. data/lib/sequel/model.rb +5 -0
  39. data/lib/sequel/model/associations.rb +80 -14
  40. data/lib/sequel/model/base.rb +182 -55
  41. data/lib/sequel/model/exceptions.rb +3 -1
  42. data/lib/sequel/plugins/association_pks.rb +6 -4
  43. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  44. data/lib/sequel/plugins/many_through_many.rb +8 -3
  45. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  46. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  47. data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
  48. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  49. data/lib/sequel/sql.rb +8 -0
  50. data/lib/sequel/version.rb +1 -1
  51. data/spec/adapters/postgres_spec.rb +43 -18
  52. data/spec/core/connection_pool_spec.rb +56 -77
  53. data/spec/core/database_spec.rb +25 -0
  54. data/spec/core/dataset_spec.rb +127 -16
  55. data/spec/core/expression_filters_spec.rb +13 -0
  56. data/spec/core/schema_spec.rb +6 -1
  57. data/spec/extensions/association_pks_spec.rb +7 -0
  58. data/spec/extensions/defaults_setter_spec.rb +64 -0
  59. data/spec/extensions/many_through_many_spec.rb +60 -4
  60. data/spec/extensions/nested_attributes_spec.rb +1 -0
  61. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  62. data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
  63. data/spec/extensions/prepared_statements_spec.rb +72 -0
  64. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  65. data/spec/extensions/to_dot_spec.rb +3 -5
  66. data/spec/integration/associations_test.rb +155 -1
  67. data/spec/integration/dataset_test.rb +8 -1
  68. data/spec/integration/plugin_test.rb +119 -0
  69. data/spec/integration/prepared_statement_test.rb +72 -1
  70. data/spec/integration/schema_test.rb +66 -8
  71. data/spec/integration/transaction_test.rb +40 -0
  72. data/spec/model/associations_spec.rb +349 -8
  73. data/spec/model/base_spec.rb +59 -0
  74. data/spec/model/hooks_spec.rb +161 -0
  75. data/spec/model/record_spec.rb +24 -0
  76. metadata +21 -4
@@ -224,7 +224,7 @@ module Sequel
224
224
  private
225
225
 
226
226
  # Use a subquery to filter rows to those related to the given associated object
227
- def many_through_many_association_filter_expression(ref, obj)
227
+ def many_through_many_association_filter_expression(op, ref, obj)
228
228
  lpks = ref[:left_primary_keys]
229
229
  lpks = lpks.first if lpks.length == 1
230
230
  edges = ref.edges
@@ -238,8 +238,13 @@ module Sequel
238
238
  last_join = ds.opts[:join].last
239
239
  last_join.table_alias || last_join.table
240
240
  end
241
- ds = ds.where(Array(ref[:final_edge][:left]).map{|x| ::Sequel::SQL::QualifiedIdentifier.new(last_alias, x)}.zip(ref.right_primary_keys.map{|k| obj.send(k)}))
242
- SQL::BooleanExpression.from_value_pairs(lpks=>ds)
241
+ exp = association_filter_key_expression(Array(ref[:final_edge][:left]).map{|x| ::Sequel::SQL::QualifiedIdentifier.new(last_alias, x)}, ref.right_primary_keys, obj)
242
+ if exp == SQL::Constants::FALSE
243
+ association_filter_handle_inversion(op, exp, Array(lpks))
244
+ else
245
+ ds = ds.where(exp).exclude(SQL::BooleanExpression.from_value_pairs(ds.opts[:select].zip([]), :OR))
246
+ association_filter_handle_inversion(op, SQL::BooleanExpression.from_value_pairs(lpks=>ds), Array(lpks))
247
+ end
243
248
  end
244
249
  end
245
250
  end
@@ -0,0 +1,140 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The prepared_statements plugin modifies the model to use prepared statements for
4
+ # instance level deletes and saves, as well as class level lookups by
5
+ # primary key.
6
+ #
7
+ # Note that this plugin is unsafe in some circumstances, as it can allow up to
8
+ # 2^N prepared statements to be created for each type of insert and update query, where
9
+ # N is the number of colums in the table. It is recommended that you use the
10
+ # +prepared_statements_safe+ plugin in addition to this plugin to reduce the number
11
+ # of prepared statements that can be created, unless you tightly control how your
12
+ # model instances are saved.
13
+ #
14
+ # This plugin probably does not work correctly with the instance filters plugin.
15
+ #
16
+ # Usage:
17
+ #
18
+ # # Make all model subclasses use prepared statements (called before loading subclasses)
19
+ # Sequel::Model.plugin :prepared_statements
20
+ #
21
+ # # Make the Album class use prepared statements
22
+ # Album.plugin :prepared_statements
23
+ module PreparedStatements
24
+ # Synchronize access to the integer sequence so that no two calls get the same integer.
25
+ MUTEX = Mutex.new
26
+
27
+ i = 0
28
+ # This plugin names prepared statements uniquely using an integer sequence, this
29
+ # lambda returns the next integer to use.
30
+ NEXT = lambda{MUTEX.synchronize{i += 1}}
31
+
32
+ # Setup the datastructure used to hold the prepared statements in the model.
33
+ def self.apply(model)
34
+ model.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{})
35
+ end
36
+
37
+ module ClassMethods
38
+ # Setup the datastructure used to hold the prepared statements in the subclass.
39
+ def inherited(subclass)
40
+ super
41
+ subclass.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{})
42
+ end
43
+
44
+ private
45
+
46
+ # Create a prepared statement based on the given dataset with a unique name for the given
47
+ # type of query and values.
48
+ def prepare_statement(ds, type, vals={})
49
+ ds.prepare(type, :"smpsp_#{NEXT.call}", vals)
50
+ end
51
+
52
+ # Return a sorted array of columns for use as a hash key.
53
+ def prepared_columns(cols)
54
+ RUBY_VERSION >= '1.9' ? cols.sort : cols.sort_by{|c| c.to_s}
55
+ end
56
+
57
+ # Return a prepared statement that can be used to delete a row from this model's dataset.
58
+ def prepared_delete
59
+ @prepared_statements[:delete] ||= prepare_statement(filter(prepared_statement_key_array(primary_key)), :delete)
60
+ end
61
+
62
+ # Return a prepared statement that can be used to insert a row using the given columns.
63
+ def prepared_insert(cols)
64
+ @prepared_statements[:insert][prepared_columns(cols)] ||= prepare_statement(dataset, :insert, prepared_statement_key_hash(cols))
65
+ end
66
+
67
+ # Return a prepared statement that can be used to insert a row using the given columns
68
+ # and return that column values for the row created.
69
+ def prepared_insert_select(cols)
70
+ if dataset.supports_insert_select?
71
+ @prepared_statements[:insert_select][prepared_columns(cols)] ||= prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)), :insert_select, prepared_statement_key_hash(cols))
72
+ end
73
+ end
74
+
75
+ # Return a prepared statement that can be used to lookup a row solely based on the primary key.
76
+ def prepared_lookup
77
+ @prepared_statements[:lookup] ||= prepare_statement(filter(prepared_statement_key_array(primary_key)), :first)
78
+ end
79
+
80
+ # Return a prepared statement that can be used to refresh a row to get new column values after insertion.
81
+ def prepared_refresh
82
+ @prepared_statements[:refresh] ||= prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)).filter(prepared_statement_key_array(primary_key)), :first)
83
+ end
84
+
85
+ # Return an array of two element arrays with the column symbol as the first entry and the
86
+ # placeholder symbol as the second entry.
87
+ def prepared_statement_key_array(keys)
88
+ Array(keys).map{|k| [k, :"$#{k}"]}
89
+ end
90
+
91
+ # Return a hash mapping column symbols to placeholder symbols.
92
+ def prepared_statement_key_hash(keys)
93
+ Hash[*(prepared_statement_key_array(keys).flatten)]
94
+ end
95
+
96
+ # Return a prepared statement that can be used to update row using the given columns.
97
+ def prepared_update(cols)
98
+ @prepared_statements[:update][prepared_columns(cols)] ||= prepare_statement(filter(prepared_statement_key_array(primary_key)), :update, prepared_statement_key_hash(cols))
99
+ end
100
+
101
+ # Use a prepared statement to query the database for the row matching the given primary key.
102
+ def primary_key_lookup(pk)
103
+ prepared_lookup.call(primary_key_hash(pk))
104
+ end
105
+ end
106
+
107
+ module InstanceMethods
108
+ private
109
+
110
+ # Use a prepared statement to delete the row.
111
+ def _delete_without_checking
112
+ model.send(:prepared_delete).call(pk_hash)
113
+ end
114
+
115
+ # Use a prepared statement to insert the values into the model's dataset.
116
+ def _insert_raw(ds)
117
+ model.send(:prepared_insert, @values.keys).call(@values)
118
+ end
119
+
120
+ # Use a prepared statement to insert the values into the model's dataset
121
+ # and return the new column values.
122
+ def _insert_select_raw(ds)
123
+ if ps = model.send(:prepared_insert_select, @values.keys)
124
+ ps.call(@values)
125
+ end
126
+ end
127
+
128
+ # Use a prepared statement to refresh this model's column values.
129
+ def _refresh_get(ds)
130
+ model.send(:prepared_refresh).call(pk_hash)
131
+ end
132
+
133
+ # Use a prepared statement to update this model's columns in the database.
134
+ def _update_without_checking(columns)
135
+ model.send(:prepared_update, columns.keys).call(columns.merge(pk_hash))
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,84 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The prepared_statements_associations plugin modifies the regular association
4
+ # load method to use a cached prepared statement to load the associations.
5
+ # It will not work on all associations, but it should skip the use of prepared
6
+ # statements for associations where it will not work, assuming you load the
7
+ # plugin before defining the associations.
8
+ #
9
+ # Usage:
10
+ #
11
+ # # Make all model subclasses more safe when using prepared statements (called before loading subclasses)
12
+ # Sequel::Model.plugin :prepared_statements_associations
13
+ #
14
+ # # Make the Album class more safe when using prepared statements
15
+ # Album.plugin :prepared_statements_associations
16
+ module PreparedStatementsAssociations
17
+ # Synchronize access to the integer sequence so that no two calls get the same integer.
18
+ MUTEX = Mutex.new
19
+
20
+ i = 0
21
+ # This plugin names prepared statements uniquely using an integer sequence, this
22
+ # lambda returns the next integer to use.
23
+ NEXT = lambda{MUTEX.synchronize{i += 1}}
24
+
25
+ module ClassMethods
26
+ # Disable prepared statement use if a block is given, or the :dataset or :conditions
27
+ # options are used, or you are cloning an association.
28
+ def associate(type, name, opts = {}, &block)
29
+ if block || opts[:dataset] || opts[:conditions] || (opts[:clone] && association_reflection(opts[:clone])[:prepared_statement] == false)
30
+ opts = opts.merge(:prepared_statement=>false)
31
+ end
32
+ super(type, name, opts, &block)
33
+ end
34
+ end
35
+
36
+ module InstanceMethods
37
+ private
38
+
39
+ # Return a bound variable hash that maps the keys in +ks+ (qualified by the +table+)
40
+ # to the values of the results of sending the methods in +vs+.
41
+ def association_bound_variable_hash(table, ks, vs)
42
+ Hash[*ks.zip(vs).map{|k, v| [:"#{table}.#{k}", send(v)]}.flatten]
43
+ end
44
+
45
+ # Given an association reflection, return a bound variable hash for the given
46
+ # association for this instance's values.
47
+ def association_bound_variables(opts)
48
+ case opts[:type]
49
+ when :many_to_one
50
+ association_bound_variable_hash(opts.associated_class.table_name, opts.primary_keys, opts[:keys])
51
+ when :one_to_many
52
+ association_bound_variable_hash(opts.associated_class.table_name, opts[:keys], opts[:primary_keys])
53
+ when :many_to_many
54
+ association_bound_variable_hash(opts[:join_table], opts[:left_keys], opts[:left_primary_keys])
55
+ when :many_through_many
56
+ opts.reverse_edges
57
+ association_bound_variable_hash(opts[:final_reverse_edge][:alias], Array(opts[:left_key]), opts[:left_primary_keys])
58
+ end
59
+ end
60
+
61
+ # Given an association reflection, return and cache a prepared statement for this association such
62
+ # that, given appropriate bound variables, the prepared statement will work correctly for any
63
+ # instance.
64
+ def association_prepared_statement(opts)
65
+ opts[:prepared_statement] ||= _associated_dataset(opts, {}).unbind.first.prepare(opts.returns_array? ? :select : :first, :"smpsap_#{NEXT.call}")
66
+ end
67
+
68
+ # If a prepared statement can be used to load the associated objects, execute it to retrieve them. Otherwise,
69
+ # fall back to the default implementation.
70
+ def _load_associated_objects(opts, dynamic_opts={})
71
+ if !opts.can_have_associated_objects?(self) || dynamic_opts[:callback] || (ps = opts[:prepared_statement]) == false
72
+ super
73
+ else
74
+ if bv = association_bound_variables(opts)
75
+ (ps || association_prepared_statement(opts)).call(bv)
76
+ else
77
+ super
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,72 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The prepared_statements_safe plugin modifies the model to reduce the number of
4
+ # prepared statements that can be created, by setting as many columns as possible
5
+ # before creating, and by changing +save_changes+ to save all columns instead of
6
+ # just the changed ones.
7
+ #
8
+ # This plugin depends on the +prepared_statements+ plugin.
9
+ #
10
+ # Usage:
11
+ #
12
+ # # Make all model subclasses more safe when using prepared statements (called before loading subclasses)
13
+ # Sequel::Model.plugin :prepared_statements_safe
14
+ #
15
+ # # Make the Album class more safe when using prepared statements
16
+ # Album.plugin :prepared_statements_safe
17
+ module PreparedStatementsSafe
18
+ # Depend on the prepared_statements plugin
19
+ def self.apply(model)
20
+ model.plugin(:prepared_statements)
21
+ end
22
+
23
+ # Set the column defaults to use when creating on the model.
24
+ def self.configure(model)
25
+ model.send(:set_prepared_statements_column_defaults)
26
+ end
27
+
28
+ module ClassMethods
29
+ # A hash with column symbol keys and default values. Instance's
30
+ # values are merged into this hash before creating to reduce the
31
+ # number of free columns (columns that may or may not be present
32
+ # in the INSERT statement), as the number of prepared statements
33
+ # that can be created is 2^N (where N is the number of free columns).
34
+ attr_reader :prepared_statements_column_defaults
35
+
36
+ # Set the column defaults to use when creating on the subclass.
37
+ def inherited(subclass)
38
+ super
39
+ subclass.send(:set_prepared_statements_column_defaults)
40
+ end
41
+
42
+ private
43
+
44
+ # Set the column defaults based on the database schema. All columns
45
+ # are set to a default value unless they are a primary key column or
46
+ # they don't have a parseable default.
47
+ def set_prepared_statements_column_defaults
48
+ h = {}
49
+ db_schema.each do |k, v|
50
+ h[k] = v[:ruby_default] if (v[:ruby_default] || !v[:default]) && !v[:primary_key]
51
+ end
52
+ @prepared_statements_column_defaults = h
53
+ end
54
+ end
55
+
56
+ module InstanceMethods
57
+ # Merge the current values into the default values to reduce the number
58
+ # of free columns.
59
+ def before_create
60
+ set_values(model.prepared_statements_column_defaults.merge(values))
61
+ super
62
+ end
63
+
64
+ # Always do a full save of all columns to reduce the number of prepared
65
+ # statements that can be used.
66
+ def save_changes(opts={})
67
+ save(opts) || false if modified?
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,59 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The prepared_statements_with_pk plugin allows Dataset#with_pk for model datasets
4
+ # to use prepared statements by extract the values of previously bound variables
5
+ # using <tt>Dataset#unbind</tt>, and attempting to use a prepared statement if the
6
+ # variables can be unbound correctly. See +Unbinder+ for details about what types of
7
+ # dataset filters can be unbound correctly.
8
+ #
9
+ # This plugin depends on the +prepared_statements+ plugin and should be considered unsafe.
10
+ # Unbinding dataset values cannot be done correctly in all cases, and use of this plugin
11
+ # in cases where not there are variables that are not unbound can lead to an denial of
12
+ # service attack by allocating an arbitrary number of prepared statements. You have been
13
+ # warned.
14
+ #
15
+ # Usage:
16
+ #
17
+ # # Make all model subclasses use prepared statements for Dataset#with_pk (called before loading subclasses)
18
+ # Sequel::Model.plugin :prepared_statements_with_pk
19
+ #
20
+ # # Make the Album class use prepared statements for Dataset#with_pk
21
+ # Album.plugin :prepared_statements_with_pk
22
+ module PreparedStatementsWithPk
23
+ # Depend on the prepared_statements plugin
24
+ def self.apply(model)
25
+ model.plugin(:prepared_statements)
26
+ end
27
+
28
+ module ClassMethods
29
+ private
30
+
31
+ # Return a prepared statement that can be used to lookup a row given a dataset for the row matching
32
+ # the primary key.
33
+ def prepared_lookup_dataset(ds)
34
+ @prepared_statements[:lookup_sql][ds.sql] ||= prepare_statement(ds.filter(prepared_statement_key_array(primary_key)), :first)
35
+ end
36
+ end
37
+
38
+ module DatasetMethods
39
+ # Use a prepared statement to find a row with the matching primary key
40
+ # inside this dataset.
41
+ def with_pk(pk)
42
+ begin
43
+ ds, bv = unbind
44
+ rescue UnbindDuplicate
45
+ super
46
+ else
47
+ begin
48
+ bv = bv.merge!(model.primary_key_hash(pk)){|k, v1, v2| ((v1 == v2) ? v1 : raise(UnbindDuplicate))}
49
+ rescue UnbindDuplicate
50
+ super
51
+ else
52
+ model.send(:prepared_lookup_dataset, ds).call(bv)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
data/lib/sequel/sql.rb CHANGED
@@ -595,6 +595,8 @@ module Sequel
595
595
  end
596
596
  when StringExpression, NumericExpression
597
597
  raise(Sequel::Error, "cannot invert #{ce.inspect}")
598
+ when Constant
599
+ CONSTANT_INVERSIONS[ce] || raise(Sequel::Error, "cannot invert #{ce.inspect}")
598
600
  else
599
601
  BooleanExpression.new(:NOT, ce)
600
602
  end
@@ -706,6 +708,12 @@ module Sequel
706
708
  NOTNULL = NegativeBooleanConstant.new(nil)
707
709
  end
708
710
 
711
+ class ComplexExpression
712
+ # A hash of the opposite for each constant, used for inverting constants.
713
+ CONSTANT_INVERSIONS = {Constants::TRUE=>Constants::FALSE, Constants::FALSE=>Constants::TRUE,
714
+ Constants::NULL=>Constants::NOTNULL, Constants::NOTNULL=>Constants::NULL}
715
+ end
716
+
709
717
  # Represents an SQL function call.
710
718
  class Function < GenericExpression
711
719
  # The array of arguments to pass to the function (may be blank)
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 3
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 23
6
+ MINOR = 24
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
@@ -909,7 +909,7 @@ describe "Postgres::Database functions, languages, and triggers" do
909
909
  after do
910
910
  @d.drop_function('tf', :if_exists=>true, :cascade=>true)
911
911
  @d.drop_function('tf', :if_exists=>true, :cascade=>true, :args=>%w'integer integer')
912
- @d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true)
912
+ @d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true) if @d.server_version < 90000
913
913
  @d.drop_table(:test) rescue nil
914
914
  end
915
915
 
@@ -940,19 +940,19 @@ describe "Postgres::Database functions, languages, and triggers" do
940
940
 
941
941
  specify "#create_language and #drop_language should create and drop languages" do
942
942
  @d.send(:create_language_sql, :plpgsql).should == 'CREATE LANGUAGE plpgsql'
943
- @d.create_language(:plpgsql, :replace=>true)
943
+ @d.create_language(:plpgsql, :replace=>true) if @d.server_version < 90000
944
944
  proc{@d.create_language(:plpgsql)}.should raise_error(Sequel::DatabaseError)
945
945
  @d.send(:drop_language_sql, :plpgsql).should == 'DROP LANGUAGE plpgsql'
946
- @d.drop_language(:plpgsql)
947
- proc{@d.drop_language(:plpgsql)}.should raise_error(Sequel::DatabaseError)
946
+ @d.drop_language(:plpgsql) if @d.server_version < 90000
947
+ proc{@d.drop_language(:plpgsql)}.should raise_error(Sequel::DatabaseError) if @d.server_version < 90000
948
948
  @d.send(:create_language_sql, :plpgsql, :replace=>true, :trusted=>true, :handler=>:a, :validator=>:b).should == (@d.server_version >= 90000 ? 'CREATE OR REPLACE TRUSTED LANGUAGE plpgsql HANDLER a VALIDATOR b' : 'CREATE TRUSTED LANGUAGE plpgsql HANDLER a VALIDATOR b')
949
949
  @d.send(:drop_language_sql, :plpgsql, :if_exists=>true, :cascade=>true).should == 'DROP LANGUAGE IF EXISTS plpgsql CASCADE'
950
950
  # Make sure if exists works
951
- @d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true)
951
+ @d.drop_language(:plpgsql, :if_exists=>true, :cascade=>true) if @d.server_version < 90000
952
952
  end
953
953
 
954
954
  specify "#create_trigger and #drop_trigger should create and drop triggers" do
955
- @d.create_language(:plpgsql)
955
+ @d.create_language(:plpgsql) if @d.server_version < 90000
956
956
  @d.create_function(:tf, 'BEGIN IF NEW.value IS NULL THEN RAISE EXCEPTION \'Blah\'; END IF; RETURN NEW; END;', :language=>:plpgsql, :returns=>:trigger)
957
957
  @d.send(:create_trigger_sql, :test, :identity, :tf, :each_row=>true).should == 'CREATE TRIGGER identity BEFORE INSERT OR UPDATE OR DELETE ON test FOR EACH ROW EXECUTE PROCEDURE tf()'
958
958
  @d.create_table(:test){String :name; Integer :value}
@@ -973,17 +973,17 @@ describe "Postgres::Database functions, languages, and triggers" do
973
973
  end
974
974
 
975
975
  if POSTGRES_DB.adapter_scheme == :postgres
976
- describe "Postgres::Dataset #use_cursor" do
977
- before(:all) do
978
- @db = POSTGRES_DB
979
- @db.create_table!(:test_cursor){Integer :x}
980
- @db.sqls.clear
981
- @ds = @db[:test_cursor]
982
- @db.transaction{1001.times{|i| @ds.insert(i)}}
983
- end
984
- after(:all) do
985
- @db.drop_table(:test) rescue nil
986
- end
976
+ describe "Postgres::Dataset #use_cursor" do
977
+ before(:all) do
978
+ @db = POSTGRES_DB
979
+ @db.create_table!(:test_cursor){Integer :x}
980
+ @db.sqls.clear
981
+ @ds = @db[:test_cursor]
982
+ @db.transaction{1001.times{|i| @ds.insert(i)}}
983
+ end
984
+ after(:all) do
985
+ @db.drop_table(:test) rescue nil
986
+ end
987
987
 
988
988
  specify "should return the same results as the non-cursor use" do
989
989
  @ds.all.should == @ds.use_cursor.all
@@ -1005,5 +1005,30 @@ describe "Postgres::Dataset #use_cursor" do
1005
1005
  @ds.check_return
1006
1006
  @ds.all.should == @ds.use_cursor.all
1007
1007
  end
1008
- end
1008
+ end
1009
+
1010
+ describe "Postgres::PG_NAMED_TYPES" do
1011
+ before do
1012
+ @db = POSTGRES_DB
1013
+ Sequel::Postgres::PG_NAMED_TYPES[:interval] = lambda{|v| v.reverse}
1014
+ @db.instance_eval do
1015
+ disconnect
1016
+ @conversion_procs = nil
1017
+ end
1018
+ end
1019
+ after do
1020
+ Sequel::Postgres::PG_NAMED_TYPES.delete(:interval)
1021
+ @db.instance_eval do
1022
+ disconnect
1023
+ @conversion_procs = nil
1024
+ end
1025
+ @db.drop_table(:foo)
1026
+ end
1027
+
1028
+ specify "should look up conversion procs by name" do
1029
+ @db.create_table!(:foo){interval :bar}
1030
+ @db[:foo].insert('21 days')
1031
+ @db[:foo].get(:bar).should == 'syad 12'
1032
+ end
1033
+ end
1009
1034
  end