sequel 3.23.0 → 3.24.0

Sign up to get free protection for your applications and to get access to all the features.
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