sequel 3.21.0 → 3.28.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 +294 -0
- data/README.rdoc +20 -6
- data/Rakefile +20 -15
- data/doc/association_basics.rdoc +210 -43
- data/doc/dataset_basics.rdoc +4 -4
- data/doc/mass_assignment.rdoc +54 -0
- data/doc/migration.rdoc +15 -538
- data/doc/model_hooks.rdoc +64 -27
- data/doc/opening_databases.rdoc +37 -10
- data/doc/prepared_statements.rdoc +16 -10
- data/doc/reflection.rdoc +8 -2
- data/doc/release_notes/3.22.0.txt +39 -0
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/release_notes/3.24.0.txt +420 -0
- data/doc/release_notes/3.25.0.txt +88 -0
- data/doc/release_notes/3.26.0.txt +88 -0
- data/doc/release_notes/3.27.0.txt +82 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/schema_modification.rdoc +547 -0
- data/doc/testing.rdoc +106 -0
- data/doc/transactions.rdoc +97 -0
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado.rb +12 -1
- data/lib/sequel/adapters/amalgalite.rb +4 -0
- data/lib/sequel/adapters/db2.rb +95 -58
- data/lib/sequel/adapters/do.rb +12 -0
- data/lib/sequel/adapters/firebird.rb +25 -203
- data/lib/sequel/adapters/ibmdb.rb +440 -0
- data/lib/sequel/adapters/informix.rb +4 -19
- data/lib/sequel/adapters/jdbc/as400.rb +0 -7
- data/lib/sequel/adapters/jdbc/db2.rb +49 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
- data/lib/sequel/adapters/jdbc/h2.rb +16 -5
- 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/oracle.rb +2 -27
- data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
- data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
- data/lib/sequel/adapters/jdbc.rb +62 -29
- data/lib/sequel/adapters/mysql.rb +22 -139
- data/lib/sequel/adapters/mysql2.rb +9 -14
- data/lib/sequel/adapters/odbc/db2.rb +21 -0
- data/lib/sequel/adapters/odbc.rb +15 -3
- data/lib/sequel/adapters/oracle.rb +17 -1
- data/lib/sequel/adapters/postgres.rb +111 -16
- data/lib/sequel/adapters/shared/access.rb +21 -0
- data/lib/sequel/adapters/shared/db2.rb +290 -0
- data/lib/sequel/adapters/shared/firebird.rb +214 -0
- data/lib/sequel/adapters/shared/informix.rb +45 -0
- data/lib/sequel/adapters/shared/mssql.rb +85 -47
- data/lib/sequel/adapters/shared/mysql.rb +50 -7
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
- data/lib/sequel/adapters/shared/oracle.rb +0 -4
- data/lib/sequel/adapters/shared/postgres.rb +75 -43
- data/lib/sequel/adapters/shared/sqlite.rb +56 -8
- data/lib/sequel/adapters/sqlite.rb +12 -11
- data/lib/sequel/adapters/swift/mysql.rb +9 -0
- data/lib/sequel/adapters/tinytds.rb +139 -7
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/connection_pool/threaded.rb +3 -2
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core.rb +6 -5
- data/lib/sequel/database/connecting.rb +5 -5
- data/lib/sequel/database/dataset.rb +1 -1
- data/lib/sequel/database/dataset_defaults.rb +1 -1
- data/lib/sequel/database/logging.rb +1 -1
- data/lib/sequel/database/misc.rb +38 -17
- data/lib/sequel/database/query.rb +50 -19
- data/lib/sequel/database/schema_generator.rb +8 -5
- data/lib/sequel/database/schema_methods.rb +52 -27
- data/lib/sequel/dataset/actions.rb +167 -48
- data/lib/sequel/dataset/features.rb +57 -8
- data/lib/sequel/dataset/graph.rb +1 -1
- data/lib/sequel/dataset/misc.rb +39 -20
- data/lib/sequel/dataset/mutation.rb +3 -3
- data/lib/sequel/dataset/prepared_statements.rb +29 -14
- data/lib/sequel/dataset/query.rb +182 -32
- data/lib/sequel/dataset/sql.rb +31 -58
- data/lib/sequel/dataset.rb +8 -0
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/columns_introspection.rb +61 -0
- data/lib/sequel/extensions/migration.rb +6 -4
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model/associations.rb +893 -309
- data/lib/sequel/model/base.rb +302 -105
- data/lib/sequel/model/errors.rb +1 -1
- data/lib/sequel/model/exceptions.rb +5 -1
- data/lib/sequel/model.rb +13 -7
- data/lib/sequel/plugins/association_pks.rb +22 -4
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/identity_map.rb +113 -6
- data/lib/sequel/plugins/many_through_many.rb +67 -5
- 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 +82 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
- data/lib/sequel/plugins/sharding.rb +12 -20
- data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
- data/lib/sequel/plugins/update_primary_key.rb +1 -1
- data/lib/sequel/plugins/xml_serializer.rb +3 -3
- data/lib/sequel/sql.rb +107 -51
- data/lib/sequel/timezones.rb +12 -3
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/mssql_spec.rb +36 -0
- data/spec/adapters/mysql_spec.rb +36 -19
- data/spec/adapters/postgres_spec.rb +115 -28
- data/spec/adapters/spec_helper.rb +6 -0
- data/spec/adapters/sqlite_spec.rb +11 -0
- data/spec/core/connection_pool_spec.rb +62 -77
- data/spec/core/database_spec.rb +244 -287
- data/spec/core/dataset_spec.rb +383 -34
- data/spec/core/expression_filters_spec.rb +159 -41
- data/spec/core/schema_spec.rb +326 -3
- data/spec/core/spec_helper.rb +45 -0
- data/spec/extensions/association_pks_spec.rb +38 -0
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/identity_map_spec.rb +162 -0
- data/spec/extensions/many_through_many_spec.rb +195 -20
- data/spec/extensions/migration_spec.rb +17 -17
- 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 +79 -0
- data/spec/extensions/prepared_statements_spec.rb +72 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
- data/spec/extensions/schema_dumper_spec.rb +2 -2
- data/spec/extensions/schema_spec.rb +12 -20
- data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
- data/spec/extensions/single_table_inheritance_spec.rb +11 -0
- data/spec/extensions/spec_helper.rb +3 -1
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/extensions/xml_serializer_spec.rb +16 -4
- data/spec/integration/associations_test.rb +405 -15
- data/spec/integration/database_test.rb +4 -2
- data/spec/integration/dataset_test.rb +240 -20
- data/spec/integration/plugin_test.rb +142 -5
- data/spec/integration/prepared_statement_test.rb +174 -95
- data/spec/integration/schema_test.rb +128 -16
- data/spec/integration/spec_helper.rb +15 -0
- data/spec/integration/transaction_test.rb +40 -0
- data/spec/integration/type_test.rb +16 -2
- data/spec/model/association_reflection_spec.rb +91 -0
- data/spec/model/associations_spec.rb +476 -5
- data/spec/model/base_spec.rb +91 -1
- data/spec/model/eager_loading_spec.rb +519 -31
- data/spec/model/hooks_spec.rb +161 -0
- data/spec/model/model_spec.rb +89 -2
- data/spec/model/plugins_spec.rb +17 -0
- data/spec/model/record_spec.rb +184 -12
- data/spec/model/spec_helper.rb +5 -0
- data/spec/model/validations_spec.rb +11 -0
- metadata +85 -34
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
def inherited(subclass)
|
|
37
|
+
super
|
|
38
|
+
subclass.instance_variable_set(:@prepared_statements_column_defaults, @prepared_statements_column_defaults) if @prepared_statements_column_defaults && !subclass.prepared_statements_column_defaults
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Set the column defaults to use when creating on the subclass.
|
|
42
|
+
def set_dataset(*)
|
|
43
|
+
x = super
|
|
44
|
+
set_prepared_statements_column_defaults
|
|
45
|
+
x
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# Set the column defaults based on the database schema. All columns
|
|
51
|
+
# are set to a default value unless they are a primary key column or
|
|
52
|
+
# they don't have a parseable default.
|
|
53
|
+
def set_prepared_statements_column_defaults
|
|
54
|
+
if db_schema
|
|
55
|
+
h = {}
|
|
56
|
+
db_schema.each do |k, v|
|
|
57
|
+
h[k] = v[:ruby_default] if (v[:ruby_default] || !v[:default]) && !v[:primary_key]
|
|
58
|
+
end
|
|
59
|
+
@prepared_statements_column_defaults = h
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
module InstanceMethods
|
|
65
|
+
# Merge the current values into the default values to reduce the number
|
|
66
|
+
# of free columns.
|
|
67
|
+
def before_create
|
|
68
|
+
if v = model.prepared_statements_column_defaults
|
|
69
|
+
set_values(v.merge(values))
|
|
70
|
+
end
|
|
71
|
+
super
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Always do a full save of all columns to reduce the number of prepared
|
|
75
|
+
# statements that can be used.
|
|
76
|
+
def save_changes(opts={})
|
|
77
|
+
save(opts) || false if modified?
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
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).map{|k, v| [SQL::QualifiedIdentifier.new(ds.model.table_name, k), v]}), :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
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# Sequel's built in Serialization plugin doesn't check for modification
|
|
4
|
+
# of the serialized objects, because it requires an extra deserialization of a potentially
|
|
5
|
+
# very large object. This plugin can detect changes in serialized values by
|
|
6
|
+
# checking whether the current deserialized value is the same as the original
|
|
7
|
+
# deserialized value. This does require deserializing the value twice, but the
|
|
8
|
+
# original deserialized value is cached.
|
|
9
|
+
#
|
|
10
|
+
# == Example
|
|
11
|
+
#
|
|
12
|
+
# require 'sequel'
|
|
13
|
+
# require 'json'
|
|
14
|
+
# class User < Sequel::Model
|
|
15
|
+
# plugin :serialization, :json, :permissions
|
|
16
|
+
# plugin :serialization_modification_detection
|
|
17
|
+
# end
|
|
18
|
+
# user = User.create(:permissions => {})
|
|
19
|
+
# user.permissions[:global] = 'read-only'
|
|
20
|
+
# user.save_changes
|
|
21
|
+
module SerializationModificationDetection
|
|
22
|
+
# Load the serialization plugin automatically.
|
|
23
|
+
def self.apply(model)
|
|
24
|
+
model.plugin :serialization
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module InstanceMethods
|
|
28
|
+
# Detect which serialized columns have changed.
|
|
29
|
+
def changed_columns
|
|
30
|
+
cc = super
|
|
31
|
+
deserialized_values.each{|c, v| cc << c if !cc.include?(c) && original_deserialized_value(c) != v}
|
|
32
|
+
cc
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# Clear the cache of original deserialized values after saving so that it doesn't
|
|
38
|
+
# show the column is modified after saving.
|
|
39
|
+
def after_save
|
|
40
|
+
super
|
|
41
|
+
@original_deserialized_values.clear if @original_deserialized_values
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Return the original deserialized value of the column, caching it to improve performance.
|
|
45
|
+
def original_deserialized_value(column)
|
|
46
|
+
(@original_deserialized_values ||= {})[column] ||= deserialize_value(column, self[column])
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -40,6 +40,18 @@ module Sequel
|
|
|
40
40
|
def new_using_server(s, values={}, &block)
|
|
41
41
|
new(values, &block).set_server(s)
|
|
42
42
|
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# Set the server for each graphed dataset to the current server
|
|
47
|
+
# unless the graphed dataset already has a server set.
|
|
48
|
+
def eager_graph_dataset(opts, eager_options)
|
|
49
|
+
ds = super
|
|
50
|
+
if s = eager_options[:self].opts[:server]
|
|
51
|
+
ds = ds.server(s) unless ds.opts[:server]
|
|
52
|
+
end
|
|
53
|
+
ds
|
|
54
|
+
end
|
|
43
55
|
end
|
|
44
56
|
|
|
45
57
|
module InstanceMethods
|
|
@@ -109,26 +121,6 @@ module Sequel
|
|
|
109
121
|
end
|
|
110
122
|
ds
|
|
111
123
|
end
|
|
112
|
-
|
|
113
|
-
private
|
|
114
|
-
|
|
115
|
-
# Set the shard of all retrieved objects to the shard of
|
|
116
|
-
# the table's dataset, or the current shard if the table's
|
|
117
|
-
# dataset does not have a shard.
|
|
118
|
-
def graph_each
|
|
119
|
-
ta = @opts[:graph][:table_aliases]
|
|
120
|
-
s = @opts[:server]
|
|
121
|
-
super do |r|
|
|
122
|
-
r.each do |k, v|
|
|
123
|
-
if ds = ta[k]
|
|
124
|
-
dss = ds.opts[:server]
|
|
125
|
-
end
|
|
126
|
-
vs = dss || s
|
|
127
|
-
v.set_server(vs) if vs && v.respond_to?(:set_server)
|
|
128
|
-
end
|
|
129
|
-
yield r
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
124
|
end
|
|
133
125
|
end
|
|
134
126
|
end
|
|
@@ -108,8 +108,10 @@ module Sequel
|
|
|
108
108
|
key = skm[subclass]
|
|
109
109
|
sti_subclass_added(key)
|
|
110
110
|
ska = [key]
|
|
111
|
+
rp = dataset.row_proc
|
|
111
112
|
subclass.set_dataset(sd.filter(SQL::QualifiedIdentifier.new(table_name, sk)=>ska), :inherited=>true)
|
|
112
113
|
subclass.instance_eval do
|
|
114
|
+
dataset.row_proc = rp
|
|
113
115
|
@sti_key = sk
|
|
114
116
|
@sti_key_array = ska
|
|
115
117
|
@sti_dataset = sd
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Sequel
|
|
2
2
|
module Plugins
|
|
3
|
-
# The update_primary_key plugin allows you to modify an
|
|
3
|
+
# The update_primary_key plugin allows you to modify an object's
|
|
4
4
|
# primary key and then save the record. Sequel does not work
|
|
5
5
|
# correctly with primary key modifications by default. Sequel
|
|
6
6
|
# is designed to work with surrogate primary keys that never need to be
|
|
@@ -205,9 +205,9 @@ module Sequel
|
|
|
205
205
|
klass.from_xml_node(node)
|
|
206
206
|
end
|
|
207
207
|
elsif cols.include?(k)
|
|
208
|
-
self[k.to_sym] = node
|
|
208
|
+
self[k.to_sym] = node.key?('nil') ? nil : node.children.first.to_s
|
|
209
209
|
elsif meths.include?("#{k}=")
|
|
210
|
-
send("#{k}=", node
|
|
210
|
+
send("#{k}=", node.key?('nil') ? nil : node.children.first.to_s)
|
|
211
211
|
else
|
|
212
212
|
raise Error, "Entry in XML not an association or column and no setter method exists: #{k}"
|
|
213
213
|
end
|
|
@@ -266,7 +266,7 @@ module Sequel
|
|
|
266
266
|
|
|
267
267
|
name_proc = model.xml_serialize_name_proc(opts)
|
|
268
268
|
x = model.xml_builder(opts)
|
|
269
|
-
x.send(name_proc[opts.fetch(:root_name, model.send(:underscore, model.name)).to_s]) do |x1|
|
|
269
|
+
x.send(name_proc[opts.fetch(:root_name, model.send(:underscore, model.name).gsub('/', '__')).to_s]) do |x1|
|
|
270
270
|
cols.each do |c|
|
|
271
271
|
attrs = {}
|
|
272
272
|
if types
|
data/lib/sequel/sql.rb
CHANGED
|
@@ -38,6 +38,16 @@ module Sequel
|
|
|
38
38
|
class LiteralString < ::String
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
# Time subclass that gets literalized with only the time value, so it operates
|
|
42
|
+
# like a standard SQL time type.
|
|
43
|
+
class SQLTime < ::Time
|
|
44
|
+
# Create a new SQLTime instance given an hour, minute, and second.
|
|
45
|
+
def self.create(hour, minute, second, usec = 0)
|
|
46
|
+
t = now
|
|
47
|
+
local(t.year, t.month, t.day, hour, minute, second, usec)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
41
51
|
# The SQL module holds classes whose instances represent SQL fragments.
|
|
42
52
|
# It also holds modules that are included in core ruby classes that
|
|
43
53
|
# make Sequel a friendly DSL.
|
|
@@ -144,14 +154,17 @@ module Sequel
|
|
|
144
154
|
# Operator symbols that take exactly two arguments
|
|
145
155
|
TWO_ARITY_OPERATORS = [:'=', :'!=', :LIKE, :'NOT LIKE', \
|
|
146
156
|
:~, :'!~', :'~*', :'!~*', :ILIKE, :'NOT ILIKE'] + \
|
|
147
|
-
INEQUALITY_OPERATORS +
|
|
157
|
+
INEQUALITY_OPERATORS + IS_OPERATORS + IN_OPERATORS
|
|
148
158
|
|
|
149
159
|
# Operator symbols that take one or more arguments
|
|
150
|
-
N_ARITY_OPERATORS = [:AND, :OR, :'||'] + MATHEMATICAL_OPERATORS
|
|
160
|
+
N_ARITY_OPERATORS = [:AND, :OR, :'||'] + MATHEMATICAL_OPERATORS + BITWISE_OPERATORS
|
|
151
161
|
|
|
152
162
|
# Operator symbols that take only a single argument
|
|
153
163
|
ONE_ARITY_OPERATORS = [:NOT, :NOOP, :'B~']
|
|
154
164
|
|
|
165
|
+
# Custom expressions that may have different syntax on different databases
|
|
166
|
+
CUSTOM_EXPRESSIONS = [:extract]
|
|
167
|
+
|
|
155
168
|
# An array of args for this object
|
|
156
169
|
attr_reader :args
|
|
157
170
|
|
|
@@ -180,6 +193,8 @@ module Sequel
|
|
|
180
193
|
args[1] = orig_args[1] if IN_OPERATORS.include?(op)
|
|
181
194
|
when *ONE_ARITY_OPERATORS
|
|
182
195
|
raise(Error, "The #{op} operator requires a single argument") unless args.length == 1
|
|
196
|
+
when *CUSTOM_EXPRESSIONS
|
|
197
|
+
# nothing
|
|
183
198
|
else
|
|
184
199
|
raise(Error, "Invalid operator #{op}")
|
|
185
200
|
end
|
|
@@ -219,14 +234,7 @@ module Sequel
|
|
|
219
234
|
# ~:a.sql_number # ~"a"
|
|
220
235
|
module BitwiseMethods
|
|
221
236
|
ComplexExpression::BITWISE_OPERATORS.each do |o|
|
|
222
|
-
|
|
223
|
-
case ce
|
|
224
|
-
when BooleanExpression, StringExpression
|
|
225
|
-
raise(Sequel::Error, "cannot apply #{o} to a non-numeric expression")
|
|
226
|
-
else
|
|
227
|
-
NumericExpression.new(o, self, ce)
|
|
228
|
-
end
|
|
229
|
-
end
|
|
237
|
+
module_eval("def #{o}(o) NumericExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__)
|
|
230
238
|
end
|
|
231
239
|
|
|
232
240
|
# Do the bitwise compliment of the self
|
|
@@ -244,16 +252,24 @@ module Sequel
|
|
|
244
252
|
# :a & :b # "a" AND "b"
|
|
245
253
|
# :a | :b # "a" OR "b"
|
|
246
254
|
# ~:a # NOT "a"
|
|
255
|
+
#
|
|
256
|
+
# One exception to this is when a NumericExpression or Integer is the argument
|
|
257
|
+
# to & or |, in which case a bitwise method will be used:
|
|
258
|
+
#
|
|
259
|
+
# :a & 1 # "a" & 1
|
|
260
|
+
# :a | (:b + 1) # "a" | ("b" + 1)
|
|
247
261
|
module BooleanMethods
|
|
248
262
|
ComplexExpression::BOOLEAN_OPERATOR_METHODS.each do |m, o|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
263
|
+
module_eval(<<-END, __FILE__, __LINE__+1)
|
|
264
|
+
def #{m}(o)
|
|
265
|
+
case o
|
|
266
|
+
when NumericExpression, Integer
|
|
267
|
+
NumericExpression.new(#{m.inspect}, self, o)
|
|
268
|
+
else
|
|
269
|
+
BooleanExpression.new(#{o.inspect}, self, o)
|
|
270
|
+
end
|
|
255
271
|
end
|
|
256
|
-
|
|
272
|
+
END
|
|
257
273
|
end
|
|
258
274
|
|
|
259
275
|
# Create a new BooleanExpression with NOT, representing the inversion of whatever self represents.
|
|
@@ -321,7 +337,7 @@ module Sequel
|
|
|
321
337
|
# doesn't use the standard function calling convention, and it
|
|
322
338
|
# doesn't work on all databases.
|
|
323
339
|
def extract(datetime_part)
|
|
324
|
-
|
|
340
|
+
NumericExpression.new(:extract, datetime_part, self)
|
|
325
341
|
end
|
|
326
342
|
|
|
327
343
|
# Return a BooleanExpression representation of +self+.
|
|
@@ -367,34 +383,12 @@ module Sequel
|
|
|
367
383
|
# 'a'.lit <= :b # a <= "b"
|
|
368
384
|
module InequalityMethods
|
|
369
385
|
ComplexExpression::INEQUALITY_OPERATORS.each do |o|
|
|
370
|
-
|
|
371
|
-
case ce
|
|
372
|
-
when BooleanExpression, TrueClass, FalseClass, NilClass, Hash, ::Array
|
|
373
|
-
raise(Error, "cannot apply #{o} to a boolean expression")
|
|
374
|
-
else
|
|
375
|
-
BooleanExpression.new(o, self, ce)
|
|
376
|
-
end
|
|
377
|
-
end
|
|
386
|
+
module_eval("def #{o}(o) BooleanExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__)
|
|
378
387
|
end
|
|
379
388
|
end
|
|
380
389
|
|
|
381
|
-
#
|
|
382
|
-
# +ComplexExpression+ subclass it is included in, so that
|
|
383
|
-
# attempting to use boolean input when initializing a +NumericExpression+
|
|
384
|
-
# or +StringExpression+ results in an error. It is not expected to be
|
|
385
|
-
# used directly.
|
|
390
|
+
# Only exists for backwards compatibility, ignore it.
|
|
386
391
|
module NoBooleanInputMethods
|
|
387
|
-
# Raise an +Error+ if one of the args would be boolean in an SQL
|
|
388
|
-
# context, otherwise call super.
|
|
389
|
-
def initialize(op, *args)
|
|
390
|
-
args.each do |a|
|
|
391
|
-
case a
|
|
392
|
-
when BooleanExpression, TrueClass, FalseClass, NilClass, Hash, ::Array
|
|
393
|
-
raise(Error, "cannot apply #{op} to a boolean expression")
|
|
394
|
-
end
|
|
395
|
-
end
|
|
396
|
-
super
|
|
397
|
-
end
|
|
398
392
|
end
|
|
399
393
|
|
|
400
394
|
# This module includes the standard mathematical methods (+, -, *, and /)
|
|
@@ -405,15 +399,26 @@ module Sequel
|
|
|
405
399
|
# :a - :b # "a" - "b"
|
|
406
400
|
# :a * :b # "a" * "b"
|
|
407
401
|
# :a / :b # "a" / "b"
|
|
402
|
+
#
|
|
403
|
+
# One exception to this is if + is called with a +String+ or +StringExpression+,
|
|
404
|
+
# in which case the || operator is used instead of the + operator:
|
|
405
|
+
#
|
|
406
|
+
# :a + 'b' # "a" || 'b'
|
|
408
407
|
module NumericMethods
|
|
409
408
|
ComplexExpression::MATHEMATICAL_OPERATORS.each do |o|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
409
|
+
module_eval("def #{o}(o) NumericExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__) unless o == :+
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Use || as the operator when called with StringExpression and String instances,
|
|
413
|
+
# and the + operator for LiteralStrings and all other types.
|
|
414
|
+
def +(ce)
|
|
415
|
+
case ce
|
|
416
|
+
when LiteralString
|
|
417
|
+
NumericExpression.new(:+, self, ce)
|
|
418
|
+
when StringExpression, String
|
|
419
|
+
StringExpression.new(:'||', self, ce)
|
|
420
|
+
else
|
|
421
|
+
NumericExpression.new(:+, self, ce)
|
|
417
422
|
end
|
|
418
423
|
end
|
|
419
424
|
end
|
|
@@ -595,10 +600,27 @@ module Sequel
|
|
|
595
600
|
end
|
|
596
601
|
when StringExpression, NumericExpression
|
|
597
602
|
raise(Sequel::Error, "cannot invert #{ce.inspect}")
|
|
603
|
+
when Constant
|
|
604
|
+
CONSTANT_INVERSIONS[ce] || raise(Sequel::Error, "cannot invert #{ce.inspect}")
|
|
598
605
|
else
|
|
599
606
|
BooleanExpression.new(:NOT, ce)
|
|
600
607
|
end
|
|
601
608
|
end
|
|
609
|
+
|
|
610
|
+
# Always use an AND operator for & on BooleanExpressions
|
|
611
|
+
def &(ce)
|
|
612
|
+
BooleanExpression.new(:AND, self, ce)
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
# Always use an OR operator for | on BooleanExpressions
|
|
616
|
+
def |(ce)
|
|
617
|
+
BooleanExpression.new(:OR, self, ce)
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
# Return self instead of creating a new object to save on memory.
|
|
621
|
+
def sql_boolean
|
|
622
|
+
self
|
|
623
|
+
end
|
|
602
624
|
end
|
|
603
625
|
|
|
604
626
|
# Represents an SQL CASE expression, used for conditional branching in SQL.
|
|
@@ -666,6 +688,21 @@ module Sequel
|
|
|
666
688
|
include CastMethods
|
|
667
689
|
include OrderMethods
|
|
668
690
|
include SubscriptMethods
|
|
691
|
+
|
|
692
|
+
# Return a BooleanExpression with the same op and args.
|
|
693
|
+
def sql_boolean
|
|
694
|
+
BooleanExpression.new(self.op, *self.args)
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
# Return a NumericExpression with the same op and args.
|
|
698
|
+
def sql_number
|
|
699
|
+
NumericExpression.new(self.op, *self.args)
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
# Return a StringExpression with the same op and args.
|
|
703
|
+
def sql_string
|
|
704
|
+
StringExpression.new(self.op, *self.args)
|
|
705
|
+
end
|
|
669
706
|
end
|
|
670
707
|
|
|
671
708
|
# Represents constants or psuedo-constants (e.g. +CURRENT_DATE+) in SQL.
|
|
@@ -706,6 +743,12 @@ module Sequel
|
|
|
706
743
|
NOTNULL = NegativeBooleanConstant.new(nil)
|
|
707
744
|
end
|
|
708
745
|
|
|
746
|
+
class ComplexExpression
|
|
747
|
+
# A hash of the opposite for each constant, used for inverting constants.
|
|
748
|
+
CONSTANT_INVERSIONS = {Constants::TRUE=>Constants::FALSE, Constants::FALSE=>Constants::TRUE,
|
|
749
|
+
Constants::NULL=>Constants::NOTNULL, Constants::NOTNULL=>Constants::NULL}
|
|
750
|
+
end
|
|
751
|
+
|
|
709
752
|
# Represents an SQL function call.
|
|
710
753
|
class Function < GenericExpression
|
|
711
754
|
# The array of arguments to pass to the function (may be blank)
|
|
@@ -834,7 +877,16 @@ module Sequel
|
|
|
834
877
|
include BitwiseMethods
|
|
835
878
|
include NumericMethods
|
|
836
879
|
include InequalityMethods
|
|
837
|
-
|
|
880
|
+
|
|
881
|
+
# Always use + for + operator for NumericExpressions.
|
|
882
|
+
def +(ce)
|
|
883
|
+
NumericExpression.new(:+, self, ce)
|
|
884
|
+
end
|
|
885
|
+
|
|
886
|
+
# Return self instead of creating a new object to save on memory.
|
|
887
|
+
def sql_number
|
|
888
|
+
self
|
|
889
|
+
end
|
|
838
890
|
end
|
|
839
891
|
|
|
840
892
|
# Represents a column/expression to order the result set by.
|
|
@@ -900,7 +952,6 @@ module Sequel
|
|
|
900
952
|
include StringMethods
|
|
901
953
|
include StringConcatenationMethods
|
|
902
954
|
include InequalityMethods
|
|
903
|
-
include NoBooleanInputMethods
|
|
904
955
|
|
|
905
956
|
# Map of [regexp, case_insenstive] to +ComplexExpression+ operator symbol
|
|
906
957
|
LIKE_MAP = {[true, true]=>:'~*', [true, false]=>:~, [false, true]=>:ILIKE, [false, false]=>:LIKE}
|
|
@@ -949,6 +1000,11 @@ module Sequel
|
|
|
949
1000
|
end
|
|
950
1001
|
end
|
|
951
1002
|
private_class_method :like_element
|
|
1003
|
+
|
|
1004
|
+
# Return self instead of creating a new object to save on memory.
|
|
1005
|
+
def sql_string
|
|
1006
|
+
self
|
|
1007
|
+
end
|
|
952
1008
|
end
|
|
953
1009
|
|
|
954
1010
|
# Represents an SQL array access, with multiple possible arguments.
|
data/lib/sequel/timezones.rb
CHANGED
|
@@ -109,6 +109,8 @@ module Sequel
|
|
|
109
109
|
else
|
|
110
110
|
Time.send(input_timezone == :utc ? :utc : :local, y, mo, d, h, mi, s)
|
|
111
111
|
end
|
|
112
|
+
when Hash
|
|
113
|
+
convert_input_timestamp([:year, :month, :day, :hour, :minute, :second].map{|x| (v[x] || v[x.to_s]).to_i}, input_timezone)
|
|
112
114
|
when Time
|
|
113
115
|
if datetime_class == DateTime
|
|
114
116
|
v.respond_to?(:to_datetime) ? v.to_datetime : string_to_datetime(v.iso8601)
|
|
@@ -121,8 +123,6 @@ module Sequel
|
|
|
121
123
|
else
|
|
122
124
|
v.respond_to?(:to_time) ? v.to_time : string_to_datetime(v.to_s)
|
|
123
125
|
end
|
|
124
|
-
when Date
|
|
125
|
-
convert_input_timestamp(v.to_s, input_timezone)
|
|
126
126
|
else
|
|
127
127
|
raise InvalidValue, "Invalid convert_input_timestamp type: #{v.inspect}"
|
|
128
128
|
end
|
|
@@ -160,7 +160,16 @@ module Sequel
|
|
|
160
160
|
# +convert_output_timestamp+.
|
|
161
161
|
def convert_timestamp(v, input_timezone)
|
|
162
162
|
begin
|
|
163
|
-
|
|
163
|
+
if v.is_a?(Date) && !v.is_a?(DateTime)
|
|
164
|
+
# Dates handled specially as they are assumed to already be in the application_timezone
|
|
165
|
+
if datetime_class == DateTime
|
|
166
|
+
DateTime.civil(v.year, v.month, v.day, 0, 0, 0, application_timezone == :local ? (defined?(Rational) ? Rational(Time.local(v.year, v.month, v.day).utc_offset, 86400) : Time.local(v.year, v.month, v.day).utc_offset/86400.0) : 0)
|
|
167
|
+
else
|
|
168
|
+
Time.send(application_timezone == :utc ? :utc : :local, v.year, v.month, v.day)
|
|
169
|
+
end
|
|
170
|
+
else
|
|
171
|
+
convert_output_timestamp(convert_input_timestamp(v, input_timezone), application_timezone)
|
|
172
|
+
end
|
|
164
173
|
rescue InvalidValue
|
|
165
174
|
raise
|
|
166
175
|
rescue => e
|
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 = 28
|
|
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
|