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.
- data/CHANGELOG +64 -0
- data/doc/association_basics.rdoc +43 -5
- data/doc/model_hooks.rdoc +64 -27
- data/doc/prepared_statements.rdoc +8 -4
- data/doc/reflection.rdoc +8 -2
- data/doc/release_notes/3.23.0.txt +1 -1
- data/doc/release_notes/3.24.0.txt +420 -0
- data/lib/sequel/adapters/db2.rb +8 -1
- data/lib/sequel/adapters/firebird.rb +25 -9
- data/lib/sequel/adapters/informix.rb +4 -19
- data/lib/sequel/adapters/jdbc.rb +34 -17
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/jdbc/informix.rb +31 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
- data/lib/sequel/adapters/postgres.rb +30 -1
- data/lib/sequel/adapters/shared/access.rb +10 -0
- data/lib/sequel/adapters/shared/informix.rb +45 -0
- data/lib/sequel/adapters/shared/mssql.rb +82 -8
- data/lib/sequel/adapters/shared/mysql.rb +25 -7
- data/lib/sequel/adapters/shared/postgres.rb +39 -6
- data/lib/sequel/adapters/shared/sqlite.rb +57 -5
- data/lib/sequel/adapters/sqlite.rb +8 -3
- data/lib/sequel/adapters/swift/mysql.rb +9 -0
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/misc.rb +6 -0
- data/lib/sequel/database/query.rb +33 -3
- data/lib/sequel/database/schema_methods.rb +6 -2
- data/lib/sequel/dataset/features.rb +6 -0
- data/lib/sequel/dataset/prepared_statements.rb +17 -2
- data/lib/sequel/dataset/query.rb +17 -0
- data/lib/sequel/dataset/sql.rb +2 -53
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model.rb +5 -0
- data/lib/sequel/model/associations.rb +80 -14
- data/lib/sequel/model/base.rb +182 -55
- data/lib/sequel/model/exceptions.rb +3 -1
- data/lib/sequel/plugins/association_pks.rb +6 -4
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/many_through_many.rb +8 -3
- data/lib/sequel/plugins/prepared_statements.rb +140 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/sql.rb +8 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +43 -18
- data/spec/core/connection_pool_spec.rb +56 -77
- data/spec/core/database_spec.rb +25 -0
- data/spec/core/dataset_spec.rb +127 -16
- data/spec/core/expression_filters_spec.rb +13 -0
- data/spec/core/schema_spec.rb +6 -1
- data/spec/extensions/association_pks_spec.rb +7 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/many_through_many_spec.rb +60 -4
- data/spec/extensions/nested_attributes_spec.rb +1 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
- data/spec/extensions/prepared_statements_spec.rb +72 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/integration/associations_test.rb +155 -1
- data/spec/integration/dataset_test.rb +8 -1
- data/spec/integration/plugin_test.rb +119 -0
- data/spec/integration/prepared_statement_test.rb +72 -1
- data/spec/integration/schema_test.rb +66 -8
- data/spec/integration/transaction_test.rb +40 -0
- data/spec/model/associations_spec.rb +349 -8
- data/spec/model/base_spec.rb +59 -0
- data/spec/model/hooks_spec.rb +161 -0
- data/spec/model/record_spec.rb +24 -0
- 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
|
-
|
242
|
-
SQL::
|
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)
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
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
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
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
|