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