sequel 4.8.0 → 4.9.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.
- checksums.yaml +4 -4
- data/CHANGELOG +48 -0
- data/doc/association_basics.rdoc +1 -1
- data/doc/opening_databases.rdoc +4 -0
- data/doc/postgresql.rdoc +27 -3
- data/doc/release_notes/4.9.0.txt +190 -0
- data/doc/security.rdoc +1 -1
- data/doc/testing.rdoc +2 -2
- data/doc/validations.rdoc +8 -0
- data/lib/sequel/adapters/jdbc.rb +5 -3
- data/lib/sequel/adapters/jdbc/derby.rb +2 -8
- data/lib/sequel/adapters/jdbc/h2.rb +2 -13
- data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -16
- data/lib/sequel/adapters/mysql2.rb +11 -1
- data/lib/sequel/adapters/postgres.rb +33 -10
- data/lib/sequel/adapters/shared/db2.rb +2 -10
- data/lib/sequel/adapters/shared/mssql.rb +10 -8
- data/lib/sequel/adapters/shared/oracle.rb +9 -24
- data/lib/sequel/adapters/shared/postgres.rb +32 -9
- data/lib/sequel/adapters/shared/sqlanywhere.rb +2 -4
- data/lib/sequel/adapters/shared/sqlite.rb +4 -7
- data/lib/sequel/database/schema_methods.rb +15 -0
- data/lib/sequel/dataset.rb +1 -1
- data/lib/sequel/dataset/actions.rb +159 -27
- data/lib/sequel/dataset/graph.rb +29 -7
- data/lib/sequel/dataset/misc.rb +6 -0
- data/lib/sequel/dataset/placeholder_literalizer.rb +164 -0
- data/lib/sequel/dataset/query.rb +2 -0
- data/lib/sequel/dataset/sql.rb +103 -91
- data/lib/sequel/extensions/current_datetime_timestamp.rb +57 -0
- data/lib/sequel/extensions/pg_array.rb +68 -106
- data/lib/sequel/extensions/pg_hstore.rb +5 -5
- data/lib/sequel/extensions/schema_dumper.rb +49 -49
- data/lib/sequel/model.rb +4 -2
- data/lib/sequel/model/associations.rb +1 -1
- data/lib/sequel/model/base.rb +136 -3
- data/lib/sequel/model/errors.rb +6 -0
- data/lib/sequel/plugins/defaults_setter.rb +1 -1
- data/lib/sequel/plugins/eager_each.rb +9 -0
- data/lib/sequel/plugins/nested_attributes.rb +2 -2
- data/lib/sequel/plugins/timestamps.rb +2 -2
- data/lib/sequel/plugins/touch.rb +2 -2
- data/lib/sequel/sql.rb +20 -15
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +70 -8
- data/spec/core/dataset_spec.rb +172 -27
- data/spec/core/expression_filters_spec.rb +3 -3
- data/spec/core/object_graph_spec.rb +17 -1
- data/spec/core/placeholder_literalizer_spec.rb +128 -0
- data/spec/core/schema_spec.rb +54 -0
- data/spec/extensions/current_datetime_timestamp_spec.rb +27 -0
- data/spec/extensions/defaults_setter_spec.rb +12 -0
- data/spec/extensions/eager_each_spec.rb +6 -0
- data/spec/extensions/nested_attributes_spec.rb +14 -2
- data/spec/extensions/pg_array_spec.rb +15 -7
- data/spec/extensions/shared_caching_spec.rb +5 -5
- data/spec/extensions/timestamps_spec.rb +9 -0
- data/spec/extensions/touch_spec.rb +9 -0
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +27 -5
- data/spec/model/eager_loading_spec.rb +32 -0
- data/spec/model/model_spec.rb +119 -9
- metadata +8 -2
@@ -143,11 +143,8 @@ module Sequel
|
|
143
143
|
# Dataset class for H2 datasets accessed via JDBC.
|
144
144
|
class Dataset < JDBC::Dataset
|
145
145
|
SELECT_CLAUSE_METHODS = clause_methods(:select, %w'select distinct columns from join where group having compounds order limit')
|
146
|
-
BITWISE_METHOD_MAP = {:& =>:BITAND, :| => :BITOR, :^ => :BITXOR}
|
147
146
|
APOS = Dataset::APOS
|
148
147
|
HSTAR = "H*".freeze
|
149
|
-
BITCOMP_OPEN = "((0 - ".freeze
|
150
|
-
BITCOMP_CLOSE = ") - 1)".freeze
|
151
148
|
ILIKE_PLACEHOLDER = ["CAST(".freeze, " AS VARCHAR_IGNORECASE)".freeze].freeze
|
152
149
|
TIME_FORMAT = "'%H:%M:%S'".freeze
|
153
150
|
|
@@ -156,16 +153,8 @@ module Sequel
|
|
156
153
|
case op
|
157
154
|
when :ILIKE, :"NOT ILIKE"
|
158
155
|
super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), [SQL::PlaceholderLiteralString.new(ILIKE_PLACEHOLDER, [args.at(0)]), args.at(1)])
|
159
|
-
when :&, :|,
|
160
|
-
sql
|
161
|
-
when :<<
|
162
|
-
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
|
163
|
-
when :>>
|
164
|
-
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
|
165
|
-
when :'B~'
|
166
|
-
sql << BITCOMP_OPEN
|
167
|
-
literal_append(sql, args.at(0))
|
168
|
-
sql << BITCOMP_CLOSE
|
156
|
+
when :&, :|, :^, :<<, :>>, :'B~'
|
157
|
+
complex_expression_emulate_append(sql, op, args)
|
169
158
|
else
|
170
159
|
super
|
171
160
|
end
|
@@ -111,7 +111,6 @@ module Sequel
|
|
111
111
|
|
112
112
|
# Dataset class for HSQLDB datasets accessed via JDBC.
|
113
113
|
class Dataset < JDBC::Dataset
|
114
|
-
BITWISE_METHOD_MAP = {:& =>:BITAND, :| => :BITOR, :^ => :BITXOR}
|
115
114
|
BOOL_TRUE = 'TRUE'.freeze
|
116
115
|
BOOL_FALSE = 'FALSE'.freeze
|
117
116
|
# HSQLDB does support common table expressions, but the support is broken.
|
@@ -124,8 +123,6 @@ module Sequel
|
|
124
123
|
APOS = Dataset::APOS
|
125
124
|
HSTAR = "H*".freeze
|
126
125
|
BLOB_OPEN = "X'".freeze
|
127
|
-
BITCOMP_OPEN = "((0 - ".freeze
|
128
|
-
BITCOMP_CLOSE = ") - 1)".freeze
|
129
126
|
DEFAULT_FROM = " FROM (VALUES (0))".freeze
|
130
127
|
TIME_FORMAT = "'%H:%M:%S'".freeze
|
131
128
|
|
@@ -134,19 +131,8 @@ module Sequel
|
|
134
131
|
case op
|
135
132
|
when :ILIKE, :"NOT ILIKE"
|
136
133
|
super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args.map{|v| SQL::Function.new(:ucase, v)})
|
137
|
-
when :&, :|,
|
138
|
-
op
|
139
|
-
sql << complex_expression_arg_pairs(args){|a, b| literal(SQL::Function.new(op, a, b))}
|
140
|
-
when :<<
|
141
|
-
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
|
142
|
-
when :>>
|
143
|
-
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
|
144
|
-
when :%
|
145
|
-
sql << complex_expression_arg_pairs(args){|a, b| "MOD(#{literal(a)}, #{literal(b)})"}
|
146
|
-
when :'B~'
|
147
|
-
sql << BITCOMP_OPEN
|
148
|
-
literal_append(sql, args.at(0))
|
149
|
-
sql << BITCOMP_CLOSE
|
134
|
+
when :&, :|, :^, :%, :<<, :>>, :'B~'
|
135
|
+
complex_expression_emulate_append(sql, op, args)
|
150
136
|
else
|
151
137
|
super
|
152
138
|
end
|
@@ -144,6 +144,7 @@ module Sequel
|
|
144
144
|
class Dataset < Sequel::Dataset
|
145
145
|
include Sequel::MySQL::DatasetMethods
|
146
146
|
include Sequel::MySQL::PreparedStatements::DatasetMethods
|
147
|
+
STREAMING_SUPPORTED = ::Mysql2::VERSION >= '0.3.12'
|
147
148
|
|
148
149
|
Database::DatasetClass = self
|
149
150
|
|
@@ -160,9 +161,18 @@ module Sequel
|
|
160
161
|
self
|
161
162
|
end
|
162
163
|
|
164
|
+
# Use streaming to implement paging if Mysql2 supports it.
|
165
|
+
def paged_each(opts=OPTS, &block)
|
166
|
+
if STREAMING_SUPPORTED
|
167
|
+
stream.each(&block)
|
168
|
+
else
|
169
|
+
super
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
163
173
|
# Return a clone of the dataset that will stream rows when iterating
|
164
174
|
# over the result set, so it can handle large datasets that
|
165
|
-
# won't fit in memory (Requires mysql 0.3.12 to have an effect).
|
175
|
+
# won't fit in memory (Requires mysql 0.3.12+ to have an effect).
|
166
176
|
def stream
|
167
177
|
clone(:stream=>true)
|
168
178
|
end
|
@@ -618,6 +618,7 @@ module Sequel
|
|
618
618
|
|
619
619
|
Database::DatasetClass = self
|
620
620
|
APOS = Sequel::Dataset::APOS
|
621
|
+
DEFAULT_CURSOR_NAME = 'sequel_cursor'.freeze
|
621
622
|
|
622
623
|
# Yield all rows returned by executing the given SQL and converting
|
623
624
|
# the types.
|
@@ -626,15 +627,22 @@ module Sequel
|
|
626
627
|
execute(sql){|res| yield_hash_rows(res, fetch_rows_set_cols(res)){|h| yield h}}
|
627
628
|
end
|
628
629
|
|
630
|
+
# Use a cursor for paging.
|
631
|
+
def paged_each(opts=OPTS, &block)
|
632
|
+
use_cursor(opts).each(&block)
|
633
|
+
end
|
634
|
+
|
629
635
|
# Uses a cursor for fetching records, instead of fetching the entire result
|
630
636
|
# set at once. Can be used to process large datasets without holding
|
631
|
-
# all rows in memory (which is what the underlying drivers do
|
637
|
+
# all rows in memory (which is what the underlying drivers may do
|
632
638
|
# by default). Options:
|
633
639
|
#
|
634
|
-
#
|
635
|
-
#
|
636
|
-
#
|
637
|
-
#
|
640
|
+
# :cursor_name :: The name assigned to the cursor (default 'sequel_cursor').
|
641
|
+
# Nested cursors require different names.
|
642
|
+
# :hold :: Declare the cursor WITH HOLD and don't use transaction around the
|
643
|
+
# cursor usage.
|
644
|
+
# :rows_per_fetch :: The number of rows per fetch (default 1000). Higher
|
645
|
+
# numbers result in fewer queries but greater memory use.
|
638
646
|
#
|
639
647
|
# Usage:
|
640
648
|
#
|
@@ -645,7 +653,19 @@ module Sequel
|
|
645
653
|
# This is untested with the prepared statement/bound variable support,
|
646
654
|
# and unlikely to work with either.
|
647
655
|
def use_cursor(opts=OPTS)
|
648
|
-
clone(:cursor=>{:rows_per_fetch=>1000
|
656
|
+
clone(:cursor=>{:rows_per_fetch=>1000}.merge(opts))
|
657
|
+
end
|
658
|
+
|
659
|
+
# Replace the WHERE clause with one that uses CURRENT OF with the given
|
660
|
+
# cursor name (or the default cursor name). This allows you to update a
|
661
|
+
# large dataset by updating individual rows while processing the dataset
|
662
|
+
# via a cursor:
|
663
|
+
#
|
664
|
+
# DB[:huge_table].use_cursor(:rows_per_fetch=>1).each do |row|
|
665
|
+
# DB[:huge_table].where_current_of.update(:column=>ruby_method(row))
|
666
|
+
# end
|
667
|
+
def where_current_of(cursor_name=DEFAULT_CURSOR_NAME)
|
668
|
+
clone(:where=>Sequel.lit(['CURRENT OF '], Sequel.identifier(cursor_name)))
|
649
669
|
end
|
650
670
|
|
651
671
|
if SEQUEL_POSTGRES_USES_PG
|
@@ -760,11 +780,14 @@ module Sequel
|
|
760
780
|
# Use a cursor to fetch groups of records at a time, yielding them to the block.
|
761
781
|
def cursor_fetch_rows(sql)
|
762
782
|
server_opts = {:server=>@opts[:server] || :read_only}
|
763
|
-
|
764
|
-
|
783
|
+
cursor = @opts[:cursor]
|
784
|
+
hold = cursor[:hold]
|
785
|
+
cursor_name = quote_identifier(cursor[:cursor_name] || DEFAULT_CURSOR_NAME)
|
786
|
+
rows_per_fetch = cursor[:rows_per_fetch].to_i
|
787
|
+
|
788
|
+
db.send(*(hold ? [:synchronize, server_opts[:server]] : [:transaction, server_opts])) do
|
765
789
|
begin
|
766
|
-
execute_ddl("DECLARE #{cursor_name} NO SCROLL CURSOR
|
767
|
-
rows_per_fetch = @opts[:cursor][:rows_per_fetch].to_i
|
790
|
+
execute_ddl("DECLARE #{cursor_name} NO SCROLL CURSOR WITH#{'OUT' unless hold} HOLD FOR #{sql}", server_opts)
|
768
791
|
rows_per_fetch = 1000 if rows_per_fetch <= 0
|
769
792
|
fetch_sql = "FETCH FORWARD #{rows_per_fetch} FROM #{cursor_name}"
|
770
793
|
cols = nil
|
@@ -256,16 +256,8 @@ module Sequel
|
|
256
256
|
|
257
257
|
def complex_expression_sql_append(sql, op, args)
|
258
258
|
case op
|
259
|
-
when :&, :|,
|
260
|
-
|
261
|
-
op = BITWISE_METHOD_MAP[op]
|
262
|
-
sql << complex_expression_arg_pairs(args){|a, b| literal(SQL::Function.new(op, a, b))}
|
263
|
-
when :<<
|
264
|
-
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
|
265
|
-
when :>>
|
266
|
-
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
|
267
|
-
when :%
|
268
|
-
sql << complex_expression_arg_pairs(args){|a, b| "MOD(#{literal(a)}, #{literal(b)})"}
|
259
|
+
when :&, :|, :^, :%, :<<, :>>
|
260
|
+
complex_expression_emulate_append(sql, op, args)
|
269
261
|
when :'B~'
|
270
262
|
literal_append(sql, SQL::Function.new(:BITNOT, *args))
|
271
263
|
when :extract
|
@@ -523,19 +523,21 @@ module Sequel
|
|
523
523
|
when :'||'
|
524
524
|
super(sql, :+, args)
|
525
525
|
when :LIKE, :"NOT LIKE"
|
526
|
-
super(sql, op, args.map{|a|
|
526
|
+
super(sql, op, args.map{|a| Sequel.lit(["(", " COLLATE #{CASE_SENSITIVE_COLLATION})"], a)})
|
527
527
|
when :ILIKE, :"NOT ILIKE"
|
528
|
-
super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args.map{|a|
|
529
|
-
when
|
530
|
-
sql
|
531
|
-
when :>>
|
532
|
-
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
|
528
|
+
super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args.map{|a| Sequel.lit(["(", " COLLATE #{CASE_INSENSITIVE_COLLATION})"], a)})
|
529
|
+
when :<<, :>>
|
530
|
+
complex_expression_emulate_append(sql, op, args)
|
533
531
|
when :extract
|
534
532
|
part = args.at(0)
|
535
533
|
raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
|
536
534
|
if part == :second
|
537
|
-
expr =
|
538
|
-
sql << DATEPART_SECOND_OPEN << format.to_s << COMMA
|
535
|
+
expr = args.at(1)
|
536
|
+
sql << DATEPART_SECOND_OPEN << format.to_s << COMMA
|
537
|
+
literal_append(sql, expr)
|
538
|
+
sql << DATEPART_SECOND_MIDDLE
|
539
|
+
literal_append(sql, expr)
|
540
|
+
sql << DATEPART_SECOND_CLOSE
|
539
541
|
else
|
540
542
|
sql << DATEPART_OPEN << format.to_s << COMMA
|
541
543
|
literal_append(sql, args.at(1))
|
@@ -254,43 +254,28 @@ module Sequel
|
|
254
254
|
APOS_RE = Dataset::APOS_RE
|
255
255
|
DOUBLE_APOS = Dataset::DOUBLE_APOS
|
256
256
|
FROM = Dataset::FROM
|
257
|
-
BITCOMP_OPEN = "((0 - ".freeze
|
258
|
-
BITCOMP_CLOSE = ") - 1)".freeze
|
259
257
|
TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S%N %z'".freeze
|
260
258
|
TIMESTAMP_OFFSET_FORMAT = "%+03i:%02i".freeze
|
261
259
|
BOOL_FALSE = "'N'".freeze
|
262
260
|
BOOL_TRUE = "'Y'".freeze
|
263
261
|
HSTAR = "H*".freeze
|
264
262
|
DUAL = ['DUAL'.freeze].freeze
|
263
|
+
BITAND_PROC = lambda{|a, b| Sequel.lit(["CAST(BITAND(", ", ", ") AS INTEGER)"], a, b)}
|
265
264
|
|
266
265
|
def complex_expression_sql_append(sql, op, args)
|
267
266
|
case op
|
268
267
|
when :&
|
269
|
-
sql
|
268
|
+
complex_expression_arg_pairs_append(sql, args, &BITAND_PROC)
|
270
269
|
when :|
|
271
|
-
sql
|
272
|
-
s1 = ''
|
273
|
-
complex_expression_sql_append(s1, :&, [a, b])
|
274
|
-
"(#{literal(a)} - #{s1} + #{literal(b)})"
|
275
|
-
end
|
270
|
+
complex_expression_arg_pairs_append(sql, args){|a, b| Sequel.lit(["(", " - ", " + ", ")"], a, complex_expression_arg_pairs([a, b], &BITAND_PROC), b)}
|
276
271
|
when :^
|
277
|
-
sql
|
278
|
-
s1 =
|
279
|
-
s2 =
|
280
|
-
|
281
|
-
complex_expression_sql_append(s2, :&, x)
|
282
|
-
"(#{s1} - #{s2})"
|
272
|
+
complex_expression_arg_pairs_append(sql, args) do |*x|
|
273
|
+
s1 = complex_expression_arg_pairs(x){|a, b| Sequel.lit(["(", " - ", " + ", ")"], a, complex_expression_arg_pairs([a, b], &BITAND_PROC), b)}
|
274
|
+
s2 = complex_expression_arg_pairs(x, &BITAND_PROC)
|
275
|
+
Sequel.lit(["(", " - ", ")"], s1, s2)
|
283
276
|
end
|
284
|
-
when :'B~'
|
285
|
-
sql
|
286
|
-
literal_append(sql, args.at(0))
|
287
|
-
sql << BITCOMP_CLOSE
|
288
|
-
when :<<
|
289
|
-
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * power(2, #{literal(b)}))"}
|
290
|
-
when :>>
|
291
|
-
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / power(2, #{literal(b)}))"}
|
292
|
-
when :%
|
293
|
-
sql << complex_expression_arg_pairs(args){|a, b| "MOD(#{literal(a)}, #{literal(b)})"}
|
277
|
+
when :%, :<<, :>>, :'B~'
|
278
|
+
complex_expression_emulate_append(sql, op, args)
|
294
279
|
else
|
295
280
|
super
|
296
281
|
end
|
@@ -1183,6 +1183,24 @@ module Sequel
|
|
1183
1183
|
end
|
1184
1184
|
end
|
1185
1185
|
|
1186
|
+
# Disables automatic use of INSERT ... RETURNING. You can still use
|
1187
|
+
# returning manually to force the use of RETURNING when inserting.
|
1188
|
+
#
|
1189
|
+
# This is designed for cases where INSERT RETURNING cannot be used,
|
1190
|
+
# such as when you are using partitioning with trigger functions
|
1191
|
+
# or conditional rules, or when you are using a PostgreSQL version
|
1192
|
+
# less than 8.2, or a PostgreSQL derivative that does not support
|
1193
|
+
# returning.
|
1194
|
+
#
|
1195
|
+
# Note that when this method is used, insert will not return the
|
1196
|
+
# primary key of the inserted row, you will have to get the primary
|
1197
|
+
# key of the inserted row before inserting via nextval, or after
|
1198
|
+
# inserting via currval or lastval (making sure to use the same
|
1199
|
+
# database connection for currval or lastval).
|
1200
|
+
def disable_insert_returning
|
1201
|
+
clone(:disable_insert_returning=>true)
|
1202
|
+
end
|
1203
|
+
|
1186
1204
|
# Return the results of an EXPLAIN query as a string
|
1187
1205
|
def explain(opts=OPTS)
|
1188
1206
|
with_sql((opts[:analyze] ? EXPLAIN_ANALYZE : EXPLAIN) + select_sql).map(QUERY_PLAN).join(CRLF)
|
@@ -1220,24 +1238,24 @@ module Sequel
|
|
1220
1238
|
# Insert given values into the database.
|
1221
1239
|
def insert(*values)
|
1222
1240
|
if @opts[:returning]
|
1223
|
-
#
|
1224
|
-
# handle it
|
1241
|
+
# Already know which columns to return, let the standard code handle it
|
1225
1242
|
super
|
1226
|
-
elsif @opts[:sql]
|
1227
|
-
#
|
1228
|
-
#
|
1229
|
-
# insert statement and return nil.
|
1243
|
+
elsif @opts[:sql] || @opts[:disable_insert_returning]
|
1244
|
+
# Raw SQL used or RETURNING disabled, just use the default behavior
|
1245
|
+
# and return nil since sequence is not known.
|
1230
1246
|
super
|
1231
1247
|
nil
|
1232
1248
|
else
|
1233
|
-
# Force the use of RETURNING with the primary key value
|
1249
|
+
# Force the use of RETURNING with the primary key value,
|
1250
|
+
# unless it has been disabled.
|
1234
1251
|
returning(insert_pk).insert(*values){|r| return r.values.first}
|
1235
1252
|
end
|
1236
1253
|
end
|
1237
1254
|
|
1238
|
-
# Insert a record returning the record inserted
|
1255
|
+
# Insert a record returning the record inserted. Always returns nil without
|
1256
|
+
# inserting a query if disable_insert_returning is used.
|
1239
1257
|
def insert_select(*values)
|
1240
|
-
returning.insert(*values){|r| return r}
|
1258
|
+
returning.insert(*values){|r| return r} unless @opts[:disable_insert_returning]
|
1241
1259
|
end
|
1242
1260
|
|
1243
1261
|
# Locks all tables in the dataset's FROM clause (but not in JOINs) with
|
@@ -1279,6 +1297,11 @@ module Sequel
|
|
1279
1297
|
true
|
1280
1298
|
end
|
1281
1299
|
|
1300
|
+
# True unless insert returning has been disabled for this dataset.
|
1301
|
+
def supports_insert_select?
|
1302
|
+
!@opts[:disable_insert_returning]
|
1303
|
+
end
|
1304
|
+
|
1282
1305
|
# PostgreSQL 9.3rc1+ supports lateral subqueries
|
1283
1306
|
def supports_lateral_subqueries?
|
1284
1307
|
server_version >= 90300
|
@@ -306,10 +306,8 @@ module Sequel
|
|
306
306
|
case op
|
307
307
|
when :'||'
|
308
308
|
super(sql, :+, args)
|
309
|
-
when
|
310
|
-
sql
|
311
|
-
when :>>
|
312
|
-
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
|
309
|
+
when :<<, :>>
|
310
|
+
complex_expression_emulate_append(sql, op, args)
|
313
311
|
when :LIKE, :"NOT LIKE"
|
314
312
|
sql << Sequel::Dataset::PAREN_OPEN
|
315
313
|
literal_append(sql, args.at(0))
|
@@ -332,8 +332,9 @@ module Sequel
|
|
332
332
|
end
|
333
333
|
|
334
334
|
DATABASE_ERROR_REGEXPS = {
|
335
|
-
/(is|are) not unique\z|UNIQUE constraint failed: .+\z/ => UniqueConstraintViolation,
|
336
|
-
/foreign key constraint failed\z
|
335
|
+
/(is|are) not unique\z|PRIMARY KEY must be unique\z|UNIQUE constraint failed: .+\z/ => UniqueConstraintViolation,
|
336
|
+
/foreign key constraint failed\z/i => ForeignKeyConstraintViolation,
|
337
|
+
/\ACHECK constraint failed/ => CheckConstraintViolation,
|
337
338
|
/\A(SQLITE ERROR 19 \(CONSTRAINT\) : )?constraint failed\z/ => ConstraintViolation,
|
338
339
|
/may not be NULL\z|NOT NULL constraint failed: .+\z/ => NotNullConstraintViolation,
|
339
340
|
}.freeze
|
@@ -539,11 +540,7 @@ module Sequel
|
|
539
540
|
sql << NOT_SPACE
|
540
541
|
complex_expression_sql_append(sql, (op == :"NOT ILIKE" ? :ILIKE : :LIKE), args)
|
541
542
|
when :^
|
542
|
-
sql
|
543
|
-
a = literal(a)
|
544
|
-
b = literal(b)
|
545
|
-
"((~(#{a} & #{b})) & (#{a} | #{b}))"
|
546
|
-
end
|
543
|
+
complex_expression_arg_pairs_append(sql, args){|a, b| Sequel.lit(["((~(", " & ", ")) & (", " | ", "))"], a, b, a, b)}
|
547
544
|
when :extract
|
548
545
|
part = args.at(0)
|
549
546
|
raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
|
@@ -134,6 +134,21 @@ module Sequel
|
|
134
134
|
end
|
135
135
|
end
|
136
136
|
|
137
|
+
# Forcibly create a join table, attempting to drop it if it already exists, then creating it.
|
138
|
+
def create_join_table!(hash, options=OPTS)
|
139
|
+
drop_table?(join_table_name(hash, options))
|
140
|
+
create_join_table(hash, options)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Creates the join table unless it already exists.
|
144
|
+
def create_join_table?(hash, options=OPTS)
|
145
|
+
if supports_create_table_if_not_exists?
|
146
|
+
create_join_table(hash, options.merge(:if_not_exists=>true))
|
147
|
+
elsif !table_exists?(join_table_name(hash, options))
|
148
|
+
create_join_table(hash, options)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
137
152
|
# Creates a table with the columns given in the provided block:
|
138
153
|
#
|
139
154
|
# DB.create_table :posts do
|
data/lib/sequel/dataset.rb
CHANGED
@@ -36,5 +36,5 @@ module Sequel
|
|
36
36
|
include SQL::StringMethods
|
37
37
|
end
|
38
38
|
|
39
|
-
require(%w"query actions features graph prepared_statements misc mutation sql", 'dataset')
|
39
|
+
require(%w"query actions features graph prepared_statements misc mutation sql placeholder_literalizer", 'dataset')
|
40
40
|
end
|
@@ -42,11 +42,7 @@ module Sequel
|
|
42
42
|
# # Iterate over all rows in the table
|
43
43
|
# DB[:table].all{|row| p row}
|
44
44
|
def all(&block)
|
45
|
-
a
|
46
|
-
each{|r| a << r}
|
47
|
-
post_load(a)
|
48
|
-
a.each(&block) if block
|
49
|
-
a
|
45
|
+
_all(block){|a| each{|r| a << r}}
|
50
46
|
end
|
51
47
|
|
52
48
|
# Returns the average value for the given column/expression.
|
@@ -434,49 +430,107 @@ module Sequel
|
|
434
430
|
import(columns, hashes.map{|h| columns.map{|c| h[c]}}, opts)
|
435
431
|
end
|
436
432
|
|
437
|
-
# Yields each row in the dataset, but interally uses multiple queries as needed
|
438
|
-
#
|
439
|
-
#
|
440
|
-
# query results in memory.
|
433
|
+
# Yields each row in the dataset, but interally uses multiple queries as needed to
|
434
|
+
# process the entire result set without keeping all rows in the dataset in memory,
|
435
|
+
# even if the underlying driver buffers all query results in memory.
|
441
436
|
#
|
442
437
|
# Because this uses multiple queries internally, in order to remain consistent,
|
443
|
-
# it also uses a transaction internally. Additionally, to
|
444
|
-
#
|
445
|
-
#
|
446
|
-
#
|
438
|
+
# it also uses a transaction internally. Additionally, to work correctly, the dataset
|
439
|
+
# must have unambiguous order. Using an ambiguous order can result in an infinite loop,
|
440
|
+
# as well as subtler bugs such as yielding duplicate rows or rows being skipped.
|
441
|
+
#
|
442
|
+
# Sequel checks that the datasets using this method have an order, but it cannot
|
443
|
+
# ensure that the order is unambiguous.
|
447
444
|
#
|
448
445
|
# Options:
|
449
446
|
# :rows_per_fetch :: The number of rows to fetch per query. Defaults to 1000.
|
447
|
+
# :strategy :: The strategy to use for paging of results. By default this is :offset,
|
448
|
+
# for using an approach with a limit and offset for every page. This can
|
449
|
+
# be set to :filter, which uses a limit and a filter that excludes
|
450
|
+
# rows from previous pages. In order for this strategy to work, you must be
|
451
|
+
# selecting the columns you are ordering by, and non of the columns can contain
|
452
|
+
# NULLs. Note that some Sequel adapters have optimized implementations that will
|
453
|
+
# use cursors or streaming regardless of the :strategy option used.
|
454
|
+
# :filter_values :: If the :strategy=>:filter option is used, this option should be a proc
|
455
|
+
# that accepts the last retreived row for the previous page and an array of
|
456
|
+
# ORDER BY expressions, and returns an array of values relating to those
|
457
|
+
# expressions for the last retrieved row. You will need to use this option
|
458
|
+
# if your ORDER BY expressions are not simple columns, if they contain
|
459
|
+
# qualified identifiers that would be ambiguous unqualified, if they contain
|
460
|
+
# any identifiers that are aliased in SELECT, and potentially other cases.
|
461
|
+
#
|
462
|
+
# Examples:
|
463
|
+
#
|
464
|
+
# DB[:table].order(:id).paged_each{|row| ...}
|
465
|
+
# # SELECT * FROM table ORDER BY id LIMIT 1000
|
466
|
+
# # SELECT * FROM table ORDER BY id LIMIT 1000 OFFSET 1000
|
467
|
+
# # ...
|
468
|
+
#
|
469
|
+
# DB[:table].order(:id).paged_each(:rows_per_fetch=>100){|row| ...}
|
470
|
+
# # SELECT * FROM table ORDER BY id LIMIT 100
|
471
|
+
# # SELECT * FROM table ORDER BY id LIMIT 100 OFFSET 100
|
472
|
+
# # ...
|
473
|
+
#
|
474
|
+
# DB[:table].order(:id).paged_each(:strategy=>:filter){|row| ...}
|
475
|
+
# # SELECT * FROM table ORDER BY id LIMIT 1000
|
476
|
+
# # SELECT * FROM table WHERE id > 1001 ORDER BY id LIMIT 1000
|
477
|
+
# # ...
|
478
|
+
#
|
479
|
+
# DB[:table].order(:table__id).paged_each(:strategy=>:filter,
|
480
|
+
# :filter_values=>proc{|row, exprs| [row[:id]]}){|row| ...}
|
481
|
+
# # SELECT * FROM table ORDER BY id LIMIT 1000
|
482
|
+
# # SELECT * FROM table WHERE id > 1001 ORDER BY id LIMIT 1000
|
483
|
+
# # ...
|
450
484
|
def paged_each(opts=OPTS)
|
451
485
|
unless @opts[:order]
|
452
486
|
raise Sequel::Error, "Dataset#paged_each requires the dataset be ordered"
|
453
487
|
end
|
454
488
|
|
455
489
|
total_limit = @opts[:limit]
|
456
|
-
offset = @opts[:offset]
|
457
|
-
|
490
|
+
offset = @opts[:offset]
|
458
491
|
if server = @opts[:server]
|
459
492
|
opts = opts.merge(:server=>server)
|
460
493
|
end
|
461
494
|
|
462
495
|
rows_per_fetch = opts[:rows_per_fetch] || 1000
|
463
|
-
|
464
|
-
|
496
|
+
strategy = if offset || total_limit
|
497
|
+
:offset
|
498
|
+
else
|
499
|
+
opts[:strategy] || :offset
|
500
|
+
end
|
465
501
|
|
466
502
|
db.transaction(opts) do
|
467
|
-
|
468
|
-
|
469
|
-
|
503
|
+
case strategy
|
504
|
+
when :filter
|
505
|
+
filter_values = opts[:filter_values] || proc{|row, exprs| exprs.map{|e| row[hash_key_symbol(e)]}}
|
506
|
+
base_ds = ds = limit(rows_per_fetch)
|
507
|
+
while ds
|
508
|
+
last_row = nil
|
509
|
+
ds.each do |row|
|
510
|
+
last_row = row
|
511
|
+
yield row
|
512
|
+
end
|
513
|
+
ds = (base_ds.where(ignore_values_preceding(last_row, &filter_values)) if last_row)
|
470
514
|
end
|
515
|
+
else
|
516
|
+
offset ||= 0
|
517
|
+
num_rows_yielded = rows_per_fetch
|
518
|
+
total_rows = 0
|
471
519
|
|
472
|
-
num_rows_yielded
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
520
|
+
while num_rows_yielded == rows_per_fetch && (total_limit.nil? || total_rows < total_limit)
|
521
|
+
if total_limit && total_rows + rows_per_fetch > total_limit
|
522
|
+
rows_per_fetch = total_limit - total_rows
|
523
|
+
end
|
524
|
+
|
525
|
+
num_rows_yielded = 0
|
526
|
+
limit(rows_per_fetch, offset).each do |row|
|
527
|
+
num_rows_yielded += 1
|
528
|
+
total_rows += 1 if total_limit
|
529
|
+
yield row
|
530
|
+
end
|
478
531
|
|
479
|
-
|
532
|
+
offset += rows_per_fetch
|
533
|
+
end
|
480
534
|
end
|
481
535
|
end
|
482
536
|
|
@@ -720,12 +774,54 @@ module Sequel
|
|
720
774
|
end
|
721
775
|
end
|
722
776
|
|
777
|
+
# Run the given SQL and return an array of all rows. If a block is given,
|
778
|
+
# each row is yielded to the block after all rows are loaded. See with_sql_each.
|
779
|
+
def with_sql_all(sql, &block)
|
780
|
+
_all(block){|a| with_sql_each(sql){|r| a << r}}
|
781
|
+
end
|
782
|
+
|
723
783
|
# Execute the given SQL and return the number of rows deleted. This exists
|
724
784
|
# solely as an optimization, replacing with_sql(sql).delete. It's significantly
|
725
785
|
# faster as it does not require cloning the current dataset.
|
726
786
|
def with_sql_delete(sql)
|
727
787
|
execute_dui(sql)
|
728
788
|
end
|
789
|
+
alias with_sql_update with_sql_delete
|
790
|
+
|
791
|
+
# Run the given SQL and yield each returned row to the block.
|
792
|
+
#
|
793
|
+
# This method should not be called on a shared dataset if the columns selected
|
794
|
+
# in the given SQL do not match the columns in the receiver.
|
795
|
+
def with_sql_each(sql)
|
796
|
+
if row_proc = @row_proc
|
797
|
+
fetch_rows(sql){|r| yield row_proc.call(r)}
|
798
|
+
else
|
799
|
+
fetch_rows(sql){|r| yield r}
|
800
|
+
end
|
801
|
+
self
|
802
|
+
end
|
803
|
+
|
804
|
+
# Run the given SQL and return the first row, or nil if no rows were returned.
|
805
|
+
# See with_sql_each.
|
806
|
+
def with_sql_first(sql)
|
807
|
+
with_sql_each(sql){|r| return r}
|
808
|
+
nil
|
809
|
+
end
|
810
|
+
|
811
|
+
# Run the given SQL and return the first value in the first row, or nil if no
|
812
|
+
# rows were returned. For this to make sense, the SQL given should select
|
813
|
+
# only a single value. See with_sql_each.
|
814
|
+
def with_sql_single_value(sql)
|
815
|
+
if r = with_sql_first(sql)
|
816
|
+
r.values.first
|
817
|
+
end
|
818
|
+
end
|
819
|
+
|
820
|
+
# Execute the given SQL and (on most databases) return the primary key of the
|
821
|
+
# inserted row.
|
822
|
+
def with_sql_insert(sql)
|
823
|
+
execute_insert(sql)
|
824
|
+
end
|
729
825
|
|
730
826
|
protected
|
731
827
|
|
@@ -754,6 +850,15 @@ module Sequel
|
|
754
850
|
|
755
851
|
private
|
756
852
|
|
853
|
+
# Internals of all and with_sql_all
|
854
|
+
def _all(block)
|
855
|
+
a = []
|
856
|
+
yield a
|
857
|
+
post_load(a)
|
858
|
+
a.each(&block) if block
|
859
|
+
a
|
860
|
+
end
|
861
|
+
|
757
862
|
# Internals of +select_hash+ and +select_hash_groups+
|
758
863
|
def _select_hash(meth, key_column, value_column)
|
759
864
|
select(*(key_column.is_a?(Array) ? key_column : [key_column]) + (value_column.is_a?(Array) ? value_column : [value_column])).
|
@@ -850,6 +955,33 @@ module Sequel
|
|
850
955
|
s.is_a?(Array) ? s.map{|c| hash_key_symbol(c)} : hash_key_symbol(s)
|
851
956
|
end
|
852
957
|
|
958
|
+
# Returns an expression that will ignore values preceding the given row, using the
|
959
|
+
# receiver's current order. This yields the row and the array of order expressions
|
960
|
+
# to the block, which should return an array of values to use.
|
961
|
+
def ignore_values_preceding(row)
|
962
|
+
@opts[:order].map{|v| v.is_a?(SQL::OrderedExpression) ? v.expression : v}
|
963
|
+
|
964
|
+
order_exprs = @opts[:order].map do |v|
|
965
|
+
if v.is_a?(SQL::OrderedExpression)
|
966
|
+
descending = v.descending
|
967
|
+
v = v.expression
|
968
|
+
else
|
969
|
+
descending = false
|
970
|
+
end
|
971
|
+
[v, descending]
|
972
|
+
end
|
973
|
+
|
974
|
+
row_values = yield(row, order_exprs.map{|e| e.first})
|
975
|
+
|
976
|
+
last_expr = []
|
977
|
+
cond = order_exprs.zip(row_values).map do |(v, descending), value|
|
978
|
+
expr = last_expr + [SQL::BooleanExpression.new(descending ? :< : :>, v, value)]
|
979
|
+
last_expr += [SQL::BooleanExpression.new(:'=', v, value)]
|
980
|
+
Sequel.&(*expr)
|
981
|
+
end
|
982
|
+
Sequel.|(*cond)
|
983
|
+
end
|
984
|
+
|
853
985
|
# Modify the identifier returned from the database based on the
|
854
986
|
# identifier_output_method.
|
855
987
|
def output_identifier(v)
|