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