sequel 3.29.0 → 3.30.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 +35 -3
- data/Rakefile +2 -1
- data/doc/association_basics.rdoc +11 -0
- data/doc/opening_databases.rdoc +2 -0
- data/doc/release_notes/3.30.0.txt +135 -0
- data/doc/testing.rdoc +17 -3
- data/lib/sequel/adapters/amalgalite.rb +2 -2
- data/lib/sequel/adapters/do/mysql.rb +5 -2
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc.rb +126 -43
- data/lib/sequel/adapters/jdbc/as400.rb +11 -3
- data/lib/sequel/adapters/jdbc/db2.rb +2 -1
- data/lib/sequel/adapters/jdbc/derby.rb +44 -19
- data/lib/sequel/adapters/jdbc/h2.rb +32 -19
- data/lib/sequel/adapters/jdbc/hsqldb.rb +21 -17
- data/lib/sequel/adapters/jdbc/jtds.rb +9 -4
- data/lib/sequel/adapters/jdbc/mssql.rb +3 -1
- data/lib/sequel/adapters/jdbc/mysql.rb +2 -1
- data/lib/sequel/adapters/jdbc/oracle.rb +21 -7
- data/lib/sequel/adapters/jdbc/postgresql.rb +3 -2
- data/lib/sequel/adapters/jdbc/sqlite.rb +2 -1
- data/lib/sequel/adapters/jdbc/sqlserver.rb +48 -18
- data/lib/sequel/adapters/mock.rb +2 -1
- data/lib/sequel/adapters/mysql.rb +4 -2
- data/lib/sequel/adapters/mysql2.rb +2 -2
- data/lib/sequel/adapters/odbc/mssql.rb +1 -1
- data/lib/sequel/adapters/openbase.rb +1 -1
- data/lib/sequel/adapters/oracle.rb +6 -6
- data/lib/sequel/adapters/postgres.rb +25 -12
- data/lib/sequel/adapters/shared/access.rb +14 -6
- data/lib/sequel/adapters/shared/db2.rb +36 -13
- data/lib/sequel/adapters/shared/firebird.rb +12 -5
- data/lib/sequel/adapters/shared/informix.rb +11 -3
- data/lib/sequel/adapters/shared/mssql.rb +94 -47
- data/lib/sequel/adapters/shared/mysql.rb +107 -49
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +2 -2
- data/lib/sequel/adapters/shared/oracle.rb +54 -27
- data/lib/sequel/adapters/shared/postgres.rb +65 -26
- data/lib/sequel/adapters/shared/progress.rb +4 -1
- data/lib/sequel/adapters/shared/sqlite.rb +36 -20
- data/lib/sequel/adapters/sqlite.rb +2 -3
- data/lib/sequel/adapters/swift/mysql.rb +3 -2
- data/lib/sequel/adapters/swift/sqlite.rb +2 -2
- data/lib/sequel/adapters/tinytds.rb +14 -8
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +7 -4
- data/lib/sequel/database/misc.rb +6 -2
- data/lib/sequel/dataset/graph.rb +33 -7
- data/lib/sequel/dataset/prepared_statements.rb +19 -5
- data/lib/sequel/dataset/sql.rb +611 -201
- data/lib/sequel/model/associations.rb +12 -5
- data/lib/sequel/model/base.rb +20 -5
- data/lib/sequel/plugins/sharding.rb +9 -29
- data/lib/sequel/sql.rb +2 -1
- data/lib/sequel/timezones.rb +14 -4
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mysql_spec.rb +10 -0
- data/spec/adapters/oracle_spec.rb +1 -1
- data/spec/core/core_sql_spec.rb +3 -1
- data/spec/core/database_spec.rb +42 -0
- data/spec/core/dataset_spec.rb +10 -3
- data/spec/core/mock_adapter_spec.rb +4 -0
- data/spec/core/object_graph_spec.rb +38 -0
- data/spec/extensions/association_autoreloading_spec.rb +1 -10
- data/spec/extensions/association_dependencies_spec.rb +2 -12
- data/spec/extensions/association_pks_spec.rb +35 -39
- data/spec/extensions/caching_spec.rb +23 -50
- data/spec/extensions/class_table_inheritance_spec.rb +30 -82
- data/spec/extensions/composition_spec.rb +18 -13
- data/spec/extensions/hook_class_methods_spec.rb +65 -91
- data/spec/extensions/identity_map_spec.rb +33 -103
- data/spec/extensions/instance_filters_spec.rb +10 -21
- data/spec/extensions/instance_hooks_spec.rb +6 -24
- data/spec/extensions/json_serializer_spec.rb +4 -5
- data/spec/extensions/lazy_attributes_spec.rb +16 -20
- data/spec/extensions/list_spec.rb +17 -39
- data/spec/extensions/many_through_many_spec.rb +135 -277
- data/spec/extensions/migration_spec.rb +18 -15
- data/spec/extensions/named_timezones_spec.rb +1 -1
- data/spec/extensions/nested_attributes_spec.rb +97 -92
- data/spec/extensions/optimistic_locking_spec.rb +9 -20
- data/spec/extensions/prepared_statements_associations_spec.rb +22 -37
- data/spec/extensions/prepared_statements_safe_spec.rb +9 -27
- data/spec/extensions/prepared_statements_spec.rb +11 -30
- data/spec/extensions/prepared_statements_with_pk_spec.rb +6 -13
- data/spec/extensions/pretty_table_spec.rb +1 -6
- data/spec/extensions/rcte_tree_spec.rb +41 -43
- data/spec/extensions/schema_dumper_spec.rb +3 -6
- data/spec/extensions/serialization_spec.rb +20 -32
- data/spec/extensions/sharding_spec.rb +66 -140
- data/spec/extensions/single_table_inheritance_spec.rb +14 -36
- data/spec/extensions/spec_helper.rb +10 -64
- data/spec/extensions/sql_expr_spec.rb +20 -60
- data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
- data/spec/extensions/timestamps_spec.rb +6 -6
- data/spec/extensions/to_dot_spec.rb +1 -2
- data/spec/extensions/touch_spec.rb +13 -14
- data/spec/extensions/tree_spec.rb +11 -26
- data/spec/extensions/update_primary_key_spec.rb +30 -24
- data/spec/extensions/validation_class_methods_spec.rb +30 -51
- data/spec/extensions/validation_helpers_spec.rb +16 -35
- data/spec/integration/dataset_test.rb +16 -4
- data/spec/integration/prepared_statement_test.rb +4 -2
- data/spec/model/eager_loading_spec.rb +16 -0
- data/spec/model/model_spec.rb +15 -1
- data/spec/model/record_spec.rb +60 -0
- metadata +23 -40
|
@@ -333,34 +333,69 @@ module Sequel
|
|
|
333
333
|
COMMA_SEPARATOR = ', '.freeze
|
|
334
334
|
FOR_SHARE = ' LOCK IN SHARE MODE'.freeze
|
|
335
335
|
SQL_CALC_FOUND_ROWS = ' SQL_CALC_FOUND_ROWS'.freeze
|
|
336
|
-
DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'from where order limit')
|
|
337
|
-
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'ignore into columns values on_duplicate_key_update')
|
|
338
|
-
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct calc_found_rows columns from join where group having compounds order limit lock')
|
|
339
|
-
UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'table set where order limit')
|
|
336
|
+
DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'delete from where order limit')
|
|
337
|
+
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'insert ignore into columns values on_duplicate_key_update')
|
|
338
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct calc_found_rows columns from join where group having compounds order limit lock')
|
|
339
|
+
UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'update table set where order limit')
|
|
340
|
+
SPACE = Dataset::SPACE
|
|
341
|
+
PAREN_OPEN = Dataset::PAREN_OPEN
|
|
342
|
+
PAREN_CLOSE = Dataset::PAREN_CLOSE
|
|
343
|
+
NOT_SPACE = Dataset::NOT_SPACE
|
|
344
|
+
FROM = Dataset::FROM
|
|
345
|
+
INSERT = Dataset::INSERT
|
|
346
|
+
COMMA = Dataset::COMMA
|
|
347
|
+
LIMIT = Dataset::LIMIT
|
|
348
|
+
REGEXP = 'REGEXP'.freeze
|
|
349
|
+
LIKE = 'LIKE'.freeze
|
|
350
|
+
BINARY = 'BINARY '.freeze
|
|
351
|
+
CONCAT = "CONCAT".freeze
|
|
352
|
+
CAST_BITCOMP_OPEN = "CAST(~".freeze
|
|
353
|
+
CAST_BITCOMP_CLOSE = " AS SIGNED INTEGER)".freeze
|
|
354
|
+
STRAIGHT_JOIN = 'STRAIGHT_JOIN'.freeze
|
|
355
|
+
NATURAL_LEFT_JOIN = 'NATURAL LEFT JOIN'.freeze
|
|
356
|
+
BACKTICK = '`'.freeze
|
|
357
|
+
EMPTY_COLUMNS = " ()".freeze
|
|
358
|
+
EMPTY_VALUES = " VALUES ()".freeze
|
|
359
|
+
IGNORE = " IGNORE".freeze
|
|
360
|
+
REPLACE = 'REPLACE'.freeze
|
|
361
|
+
ON_DUPLICATE_KEY_UPDATE = " ON DUPLICATE KEY UPDATE ".freeze
|
|
362
|
+
EQ_VALUES = '=VALUES('.freeze
|
|
363
|
+
EQ = '='.freeze
|
|
340
364
|
|
|
341
365
|
# MySQL specific syntax for LIKE/REGEXP searches, as well as
|
|
342
366
|
# string concatenation.
|
|
343
|
-
def
|
|
367
|
+
def complex_expression_sql_append(sql, op, args)
|
|
344
368
|
case op
|
|
345
369
|
when :IN, :"NOT IN"
|
|
346
370
|
ds = args.at(1)
|
|
347
371
|
if ds.is_a?(Sequel::Dataset) && ds.opts[:limit]
|
|
348
|
-
super(op, [args.at(0), ds.from_self])
|
|
372
|
+
super(sql, op, [args.at(0), ds.from_self])
|
|
349
373
|
else
|
|
350
374
|
super
|
|
351
375
|
end
|
|
352
376
|
when :~, :'!~', :'~*', :'!~*', :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
|
|
353
|
-
|
|
377
|
+
sql << PAREN_OPEN
|
|
378
|
+
literal_append(sql, args.at(0))
|
|
379
|
+
sql << SPACE
|
|
380
|
+
sql << 'NOT ' if [:'NOT LIKE', :'NOT ILIKE', :'!~', :'!~*'].include?(op)
|
|
381
|
+
sql << ([:~, :'!~', :'~*', :'!~*'].include?(op) ? REGEXP : LIKE)
|
|
382
|
+
sql << SPACE
|
|
383
|
+
sql << BINARY if [:~, :'!~', :LIKE, :'NOT LIKE'].include?(op)
|
|
384
|
+
literal_append(sql, args.at(1))
|
|
385
|
+
sql << PAREN_CLOSE
|
|
354
386
|
when :'||'
|
|
355
387
|
if args.length > 1
|
|
356
|
-
|
|
388
|
+
sql << CONCAT
|
|
389
|
+
array_sql_append(sql, args)
|
|
357
390
|
else
|
|
358
|
-
|
|
391
|
+
literal_append(sql, args.at(0))
|
|
359
392
|
end
|
|
360
393
|
when :'B~'
|
|
361
|
-
|
|
394
|
+
sql << CAST_BITCOMP_OPEN
|
|
395
|
+
literal_append(sql, args.at(0))
|
|
396
|
+
sql << CAST_BITCOMP_CLOSE
|
|
362
397
|
else
|
|
363
|
-
super
|
|
398
|
+
super
|
|
364
399
|
end
|
|
365
400
|
end
|
|
366
401
|
|
|
@@ -409,9 +444,12 @@ module Sequel
|
|
|
409
444
|
# STRAIGHT_JOIN.
|
|
410
445
|
def join_type_sql(join_type)
|
|
411
446
|
case join_type
|
|
412
|
-
when :straight
|
|
413
|
-
|
|
414
|
-
|
|
447
|
+
when :straight
|
|
448
|
+
STRAIGHT_JOIN
|
|
449
|
+
when :natural_inner
|
|
450
|
+
NATURAL_LEFT_JOIN
|
|
451
|
+
else
|
|
452
|
+
super
|
|
415
453
|
end
|
|
416
454
|
end
|
|
417
455
|
|
|
@@ -452,7 +490,9 @@ module Sequel
|
|
|
452
490
|
|
|
453
491
|
# MySQL specific syntax for inserting multiple values at once.
|
|
454
492
|
def multi_insert_sql(columns, values)
|
|
455
|
-
|
|
493
|
+
sql = LiteralString.new('VALUES ')
|
|
494
|
+
expression_list_append(sql, values.map{|r| Array(r)})
|
|
495
|
+
[insert_sql(columns, sql)]
|
|
456
496
|
end
|
|
457
497
|
|
|
458
498
|
# MySQL uses the number of rows actually modified in the update,
|
|
@@ -462,8 +502,8 @@ module Sequel
|
|
|
462
502
|
end
|
|
463
503
|
|
|
464
504
|
# MySQL uses the nonstandard ` (backtick) for quoting identifiers.
|
|
465
|
-
def
|
|
466
|
-
|
|
505
|
+
def quoted_identifier_append(sql, c)
|
|
506
|
+
sql << BACKTICK << c.to_s << BACKTICK
|
|
467
507
|
end
|
|
468
508
|
|
|
469
509
|
# MySQL specific syntax for REPLACE (aka UPSERT, or update if exists,
|
|
@@ -501,13 +541,6 @@ module Sequel
|
|
|
501
541
|
false
|
|
502
542
|
end
|
|
503
543
|
|
|
504
|
-
protected
|
|
505
|
-
|
|
506
|
-
# If this is an replace instead of an insert, use replace instead
|
|
507
|
-
def _insert_sql
|
|
508
|
-
@opts[:replace] ? clause_sql(:replace) : super
|
|
509
|
-
end
|
|
510
|
-
|
|
511
544
|
private
|
|
512
545
|
|
|
513
546
|
# MySQL supports the ORDER BY and LIMIT clauses for DELETE statements
|
|
@@ -519,7 +552,10 @@ module Sequel
|
|
|
519
552
|
# from, but include the others for the purposes of selecting rows.
|
|
520
553
|
def delete_from_sql(sql)
|
|
521
554
|
if joined_dataset?
|
|
522
|
-
sql <<
|
|
555
|
+
sql << SPACE
|
|
556
|
+
source_list_append(sql, @opts[:from][0..0])
|
|
557
|
+
sql << FROM
|
|
558
|
+
source_list_append(sql, @opts[:from])
|
|
523
559
|
select_join_sql(sql)
|
|
524
560
|
else
|
|
525
561
|
super
|
|
@@ -536,7 +572,7 @@ module Sequel
|
|
|
536
572
|
def insert_columns_sql(sql)
|
|
537
573
|
values = opts[:values]
|
|
538
574
|
if values.is_a?(Array) && values.empty?
|
|
539
|
-
sql <<
|
|
575
|
+
sql << EMPTY_COLUMNS
|
|
540
576
|
else
|
|
541
577
|
super
|
|
542
578
|
end
|
|
@@ -544,19 +580,57 @@ module Sequel
|
|
|
544
580
|
|
|
545
581
|
# MySQL supports INSERT IGNORE INTO
|
|
546
582
|
def insert_ignore_sql(sql)
|
|
547
|
-
sql <<
|
|
583
|
+
sql << IGNORE if opts[:insert_ignore]
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
# If this is an replace instead of an insert, use replace instead
|
|
587
|
+
def insert_insert_sql(sql)
|
|
588
|
+
sql << (@opts[:replace] ? REPLACE : INSERT)
|
|
548
589
|
end
|
|
549
590
|
|
|
550
591
|
# MySQL supports INSERT ... ON DUPLICATE KEY UPDATE
|
|
551
592
|
def insert_on_duplicate_key_update_sql(sql)
|
|
552
|
-
|
|
593
|
+
if update_cols = opts[:on_duplicate_key_update]
|
|
594
|
+
update_vals = nil
|
|
595
|
+
|
|
596
|
+
if update_cols.empty?
|
|
597
|
+
update_cols = columns
|
|
598
|
+
elsif update_cols.last.is_a?(Hash)
|
|
599
|
+
update_vals = update_cols.last
|
|
600
|
+
update_cols = update_cols[0..-2]
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
sql << ON_DUPLICATE_KEY_UPDATE
|
|
604
|
+
c = false
|
|
605
|
+
co = COMMA
|
|
606
|
+
values = EQ_VALUES
|
|
607
|
+
endp = PAREN_CLOSE
|
|
608
|
+
update_cols.each do |col|
|
|
609
|
+
sql << co if c
|
|
610
|
+
quote_identifier_append(sql, col)
|
|
611
|
+
sql << values
|
|
612
|
+
quote_identifier_append(sql, col)
|
|
613
|
+
sql << endp
|
|
614
|
+
c ||= true
|
|
615
|
+
end
|
|
616
|
+
if update_vals
|
|
617
|
+
eq = EQ
|
|
618
|
+
update_vals.map do |col,v|
|
|
619
|
+
sql << co if c
|
|
620
|
+
quote_identifier_append(sql, col)
|
|
621
|
+
sql << eq
|
|
622
|
+
literal_append(sql, v)
|
|
623
|
+
c ||= true
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
end
|
|
553
627
|
end
|
|
554
628
|
|
|
555
629
|
# MySQL doesn't use the standard DEFAULT VALUES for empty values.
|
|
556
630
|
def insert_values_sql(sql)
|
|
557
631
|
values = opts[:values]
|
|
558
632
|
if values.is_a?(Array) && values.empty?
|
|
559
|
-
sql <<
|
|
633
|
+
sql << EMPTY_VALUES
|
|
560
634
|
else
|
|
561
635
|
super
|
|
562
636
|
end
|
|
@@ -564,7 +638,10 @@ module Sequel
|
|
|
564
638
|
|
|
565
639
|
# MySQL allows a LIMIT in DELETE and UPDATE statements.
|
|
566
640
|
def limit_sql(sql)
|
|
567
|
-
|
|
641
|
+
if l = @opts[:limit]
|
|
642
|
+
sql << LIMIT
|
|
643
|
+
literal_append(sql, @opts[:limit])
|
|
644
|
+
end
|
|
568
645
|
end
|
|
569
646
|
alias delete_limit_sql limit_sql
|
|
570
647
|
alias update_limit_sql limit_sql
|
|
@@ -579,25 +656,6 @@ module Sequel
|
|
|
579
656
|
BOOL_TRUE
|
|
580
657
|
end
|
|
581
658
|
|
|
582
|
-
# MySQL specific syntax for ON DUPLICATE KEY UPDATE
|
|
583
|
-
def on_duplicate_key_update_sql
|
|
584
|
-
if update_cols = opts[:on_duplicate_key_update]
|
|
585
|
-
update_vals = nil
|
|
586
|
-
|
|
587
|
-
if update_cols.empty?
|
|
588
|
-
update_cols = columns
|
|
589
|
-
elsif update_cols.last.is_a?(Hash)
|
|
590
|
-
update_vals = update_cols.last
|
|
591
|
-
update_cols = update_cols[0..-2]
|
|
592
|
-
end
|
|
593
|
-
|
|
594
|
-
updating = update_cols.map{|c| "#{quote_identifier(c)}=VALUES(#{quote_identifier(c)})" }
|
|
595
|
-
updating += update_vals.map{|c,v| "#{quote_identifier(c)}=#{literal(v)}" } if update_vals
|
|
596
|
-
|
|
597
|
-
" ON DUPLICATE KEY UPDATE #{updating.join(COMMA_SEPARATOR)}"
|
|
598
|
-
end
|
|
599
|
-
end
|
|
600
|
-
|
|
601
659
|
# MySQL does not support the SQL WITH clause for SELECT statements
|
|
602
660
|
def select_clause_methods
|
|
603
661
|
SELECT_CLAUSE_METHODS
|
|
@@ -63,8 +63,8 @@ module Sequel
|
|
|
63
63
|
module CallableStatementMethods
|
|
64
64
|
# Extend given dataset with this module so subselects inside subselects in
|
|
65
65
|
# prepared statements work.
|
|
66
|
-
def
|
|
67
|
-
ps = ds.to_prepared_statement(:select)
|
|
66
|
+
def subselect_sql_append(sql, ds)
|
|
67
|
+
ps = ds.to_prepared_statement(:select).clone(:append_sql => sql)
|
|
68
68
|
ps.extend(CallableStatementMethods)
|
|
69
69
|
ps = ps.bind(@opts[:bind_vars]) if @opts[:bind_vars]
|
|
70
70
|
ps.prepared_args = prepared_args
|
|
@@ -177,27 +177,53 @@ module Sequel
|
|
|
177
177
|
module DatasetMethods
|
|
178
178
|
include EmulateOffsetWithRowNumber
|
|
179
179
|
|
|
180
|
-
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct columns from join where group having compounds order lock')
|
|
180
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct columns from join where group having compounds order lock')
|
|
181
181
|
ROW_NUMBER_EXPRESSION = 'ROWNUM'.lit.freeze
|
|
182
|
+
SPACE = Dataset::SPACE
|
|
183
|
+
APOS = Dataset::APOS
|
|
184
|
+
APOS_RE = Dataset::APOS_RE
|
|
185
|
+
DOUBLE_APOS = Dataset::DOUBLE_APOS
|
|
186
|
+
FROM = Dataset::FROM
|
|
187
|
+
BITCOMP_OPEN = "((0 - ".freeze
|
|
188
|
+
BITCOMP_CLOSE = ") - 1)".freeze
|
|
189
|
+
ILIKE_0 = "(UPPER(".freeze
|
|
190
|
+
ILIKE_1 = ") ".freeze
|
|
191
|
+
ILIKE_2 = ' UPPER('.freeze
|
|
192
|
+
ILIKE_3 = "))".freeze
|
|
193
|
+
LIKE = 'LIKE'.freeze
|
|
194
|
+
NOT_LIKE = 'NOT LIKE'.freeze
|
|
195
|
+
TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S%N %z'".freeze
|
|
196
|
+
TIMESTAMP_OFFSET_FORMAT = "%+03i:%02i".freeze
|
|
197
|
+
BOOL_FALSE = "'N'".freeze
|
|
198
|
+
BOOL_TRUE = "'Y'".freeze
|
|
199
|
+
HSTAR = "H*".freeze
|
|
200
|
+
DUAL = ['DUAL'.freeze].freeze
|
|
182
201
|
|
|
183
202
|
# Oracle needs to emulate bitwise operators and ILIKE/NOT ILIKE operators.
|
|
184
|
-
def
|
|
203
|
+
def complex_expression_sql_append(sql, op, args)
|
|
185
204
|
case op
|
|
186
205
|
when :&
|
|
187
|
-
complex_expression_arg_pairs(args){|a, b| "CAST(BITAND(#{literal(a)}, #{literal(b)}) AS INTEGER)"}
|
|
206
|
+
sql << complex_expression_arg_pairs(args){|a, b| "CAST(BITAND(#{literal(a)}, #{literal(b)}) AS INTEGER)"}
|
|
188
207
|
when :|
|
|
189
|
-
complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} - #{complex_expression_sql(:&, [a, b])} + #{literal(b)})"}
|
|
208
|
+
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} - #{complex_expression_sql(:&, [a, b])} + #{literal(b)})"}
|
|
190
209
|
when :^
|
|
191
|
-
complex_expression_arg_pairs(args){|*x| "(#{complex_expression_sql(:|, x)} - #{complex_expression_sql(:&, x)})"}
|
|
210
|
+
sql << complex_expression_arg_pairs(args){|*x| "(#{complex_expression_sql(:|, x)} - #{complex_expression_sql(:&, x)})"}
|
|
192
211
|
when :'B~'
|
|
193
|
-
|
|
212
|
+
sql << BITCOMP_OPEN
|
|
213
|
+
literal_append(sql, args.at(0))
|
|
214
|
+
sql << BITCOMP_CLOSE
|
|
194
215
|
when :<<
|
|
195
|
-
complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * power(2, #{literal b}))"}
|
|
216
|
+
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * power(2, #{literal b}))"}
|
|
196
217
|
when :>>
|
|
197
|
-
complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / power(2, #{literal b}))"}
|
|
218
|
+
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / power(2, #{literal b}))"}
|
|
198
219
|
when :ILIKE, :'NOT ILIKE'
|
|
199
|
-
|
|
200
|
-
|
|
220
|
+
sql << ILIKE_0
|
|
221
|
+
literal_append(sql, args.at(0))
|
|
222
|
+
sql << ILIKE_1
|
|
223
|
+
sql << (op == :ILIKE ? LIKE : NOT_LIKE)
|
|
224
|
+
sql<< ILIKE_2
|
|
225
|
+
literal_append(sql, args.at(1))
|
|
226
|
+
sql << ILIKE_3
|
|
201
227
|
else
|
|
202
228
|
super
|
|
203
229
|
end
|
|
@@ -206,9 +232,9 @@ module Sequel
|
|
|
206
232
|
# Oracle doesn't support CURRENT_TIME, as it doesn't have
|
|
207
233
|
# a type for storing just time values without a date, so
|
|
208
234
|
# use CURRENT_TIMESTAMP in its place.
|
|
209
|
-
def
|
|
235
|
+
def constant_sql_append(sql, c)
|
|
210
236
|
if c == :CURRENT_TIME
|
|
211
|
-
super(:CURRENT_TIMESTAMP)
|
|
237
|
+
super(sql, :CURRENT_TIMESTAMP)
|
|
212
238
|
else
|
|
213
239
|
super
|
|
214
240
|
end
|
|
@@ -244,7 +270,9 @@ module Sequel
|
|
|
244
270
|
# Lock doesn't work in subselects, so don't use a subselect when locking.
|
|
245
271
|
# Don't use a subselect if custom SQL is used, as it breaks somethings.
|
|
246
272
|
ds = ds.from_self unless @opts[:lock]
|
|
247
|
-
|
|
273
|
+
sql = @opts[:append_sql] || ''
|
|
274
|
+
subselect_sql_append(sql, ds.where(SQL::ComplexExpression.new(:<=, ROW_NUMBER_EXPRESSION, limit)))
|
|
275
|
+
sql
|
|
248
276
|
else
|
|
249
277
|
super
|
|
250
278
|
end
|
|
@@ -289,13 +317,14 @@ module Sequel
|
|
|
289
317
|
|
|
290
318
|
# Oracle doesn't support the use of AS when aliasing a dataset. It doesn't require
|
|
291
319
|
# the use of AS anywhere, so this disables it in all cases.
|
|
292
|
-
def
|
|
293
|
-
|
|
320
|
+
def as_sql_append(sql, aliaz)
|
|
321
|
+
sql << SPACE
|
|
322
|
+
quote_identifier_append(sql, aliaz)
|
|
294
323
|
end
|
|
295
324
|
|
|
296
325
|
# The strftime format to use when literalizing the time.
|
|
297
326
|
def default_timestamp_format
|
|
298
|
-
|
|
327
|
+
TIMESTAMP_FORMAT
|
|
299
328
|
end
|
|
300
329
|
|
|
301
330
|
# If this dataset is associated with a sequence, return the most recently
|
|
@@ -307,7 +336,7 @@ module Sequel
|
|
|
307
336
|
|
|
308
337
|
# Use a colon for the timestamp offset, since Oracle appears to require it.
|
|
309
338
|
def format_timestamp_offset(hour, minute)
|
|
310
|
-
sprintf(
|
|
339
|
+
sprintf(TIMESTAMP_OFFSET_FORMAT, hour, minute)
|
|
311
340
|
end
|
|
312
341
|
|
|
313
342
|
# Oracle doesn't support empty values when inserting.
|
|
@@ -316,26 +345,23 @@ module Sequel
|
|
|
316
345
|
end
|
|
317
346
|
|
|
318
347
|
# Use string in hex format for blob data.
|
|
319
|
-
def
|
|
320
|
-
|
|
321
|
-
v.each_byte{|x| blob << sprintf('%02x', x)}
|
|
322
|
-
blob << "'"
|
|
323
|
-
blob
|
|
348
|
+
def literal_blob_append(sql, v)
|
|
349
|
+
sql << APOS << v.unpack(HSTAR).first << APOS
|
|
324
350
|
end
|
|
325
351
|
|
|
326
352
|
# Oracle uses 'N' for false values.
|
|
327
353
|
def literal_false
|
|
328
|
-
|
|
354
|
+
BOOL_FALSE
|
|
329
355
|
end
|
|
330
356
|
|
|
331
357
|
# Oracle uses the SQL standard of only doubling ' inside strings.
|
|
332
|
-
def
|
|
333
|
-
|
|
358
|
+
def literal_string_append(sql, v)
|
|
359
|
+
sql << APOS << v.gsub(APOS_RE, DOUBLE_APOS) << APOS
|
|
334
360
|
end
|
|
335
361
|
|
|
336
362
|
# Oracle uses 'Y' for true values.
|
|
337
363
|
def literal_true
|
|
338
|
-
|
|
364
|
+
BOOL_TRUE
|
|
339
365
|
end
|
|
340
366
|
|
|
341
367
|
# Use the Oracle-specific SQL clauses (no limit, since it is emulated).
|
|
@@ -348,7 +374,8 @@ module Sequel
|
|
|
348
374
|
# so add the dummy DUAL table if the dataset doesn't select
|
|
349
375
|
# from a table.
|
|
350
376
|
def select_from_sql(sql)
|
|
351
|
-
sql <<
|
|
377
|
+
sql << FROM
|
|
378
|
+
source_list_append(sql, @opts[:from] || DUAL)
|
|
352
379
|
end
|
|
353
380
|
end
|
|
354
381
|
end
|
|
@@ -638,29 +638,43 @@ module Sequel
|
|
|
638
638
|
BOOL_FALSE = 'false'.freeze
|
|
639
639
|
BOOL_TRUE = 'true'.freeze
|
|
640
640
|
COMMA_SEPARATOR = ', '.freeze
|
|
641
|
-
DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'from using where')
|
|
642
|
-
DELETE_CLAUSE_METHODS_91 = Dataset.clause_methods(:delete, %w'with from using where returning')
|
|
641
|
+
DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'delete from using where')
|
|
642
|
+
DELETE_CLAUSE_METHODS_91 = Dataset.clause_methods(:delete, %w'with delete from using where returning')
|
|
643
643
|
EXCLUSIVE = 'EXCLUSIVE'.freeze
|
|
644
644
|
EXPLAIN = 'EXPLAIN '.freeze
|
|
645
645
|
EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
|
|
646
646
|
FOR_SHARE = ' FOR SHARE'.freeze
|
|
647
|
-
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'into columns values')
|
|
648
|
-
INSERT_CLAUSE_METHODS_82 = Dataset.clause_methods(:insert, %w'into columns values returning')
|
|
649
|
-
INSERT_CLAUSE_METHODS_91 = Dataset.clause_methods(:insert, %w'with into columns values returning')
|
|
647
|
+
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'insert into columns values')
|
|
648
|
+
INSERT_CLAUSE_METHODS_82 = Dataset.clause_methods(:insert, %w'insert into columns values returning')
|
|
649
|
+
INSERT_CLAUSE_METHODS_91 = Dataset.clause_methods(:insert, %w'with insert into columns values returning')
|
|
650
650
|
LOCK = 'LOCK TABLE %s IN %s MODE'.freeze
|
|
651
651
|
NULL = LiteralString.new('NULL').freeze
|
|
652
652
|
PG_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
|
|
653
653
|
QUERY_PLAN = 'QUERY PLAN'.to_sym
|
|
654
654
|
ROW_EXCLUSIVE = 'ROW EXCLUSIVE'.freeze
|
|
655
655
|
ROW_SHARE = 'ROW SHARE'.freeze
|
|
656
|
-
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit lock')
|
|
657
|
-
SELECT_CLAUSE_METHODS_84 = Dataset.clause_methods(:select, %w'with distinct columns from join where group having window compounds order limit lock')
|
|
656
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct columns from join where group having compounds order limit lock')
|
|
657
|
+
SELECT_CLAUSE_METHODS_84 = Dataset.clause_methods(:select, %w'with select distinct columns from join where group having window compounds order limit lock')
|
|
658
658
|
SHARE = 'SHARE'.freeze
|
|
659
659
|
SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
|
|
660
660
|
SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
|
|
661
661
|
SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
|
|
662
|
-
UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'table set from where')
|
|
663
|
-
UPDATE_CLAUSE_METHODS_91 = Dataset.clause_methods(:update, %w'with table set from where returning')
|
|
662
|
+
UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'update table set from where')
|
|
663
|
+
UPDATE_CLAUSE_METHODS_91 = Dataset.clause_methods(:update, %w'with update table set from where returning')
|
|
664
|
+
SPACE = Dataset::SPACE
|
|
665
|
+
FROM = Dataset::FROM
|
|
666
|
+
APOS = Dataset::APOS
|
|
667
|
+
APOS_RE = Dataset::APOS_RE
|
|
668
|
+
DOUBLE_APOS = Dataset::DOUBLE_APOS
|
|
669
|
+
PAREN_OPEN = Dataset::PAREN_OPEN
|
|
670
|
+
PAREN_CLOSE = Dataset::PAREN_CLOSE
|
|
671
|
+
COMMA = Dataset::COMMA
|
|
672
|
+
AS = Dataset::AS
|
|
673
|
+
XOR_OP = ' # '.freeze
|
|
674
|
+
CRLF = "\r\n".freeze
|
|
675
|
+
BLOB_RE = /[\000-\037\047\134\177-\377]/n.freeze
|
|
676
|
+
WINDOW = " WINDOW ".freeze
|
|
677
|
+
EMPTY_STRING = ''.freeze
|
|
664
678
|
|
|
665
679
|
# Shared methods for prepared statements when used with PostgreSQL databases.
|
|
666
680
|
module PreparedStatementMethods
|
|
@@ -698,10 +712,16 @@ module Sequel
|
|
|
698
712
|
|
|
699
713
|
# Handle converting the ruby xor operator (^) into the
|
|
700
714
|
# PostgreSQL xor operator (#).
|
|
701
|
-
def
|
|
715
|
+
def complex_expression_sql_append(sql, op, args)
|
|
702
716
|
case op
|
|
703
717
|
when :^
|
|
704
|
-
|
|
718
|
+
j = XOR_OP
|
|
719
|
+
c = false
|
|
720
|
+
args.each do |a|
|
|
721
|
+
sql << j if c
|
|
722
|
+
literal_append(sql, a)
|
|
723
|
+
c ||= true
|
|
724
|
+
end
|
|
705
725
|
else
|
|
706
726
|
super
|
|
707
727
|
end
|
|
@@ -714,7 +734,7 @@ module Sequel
|
|
|
714
734
|
|
|
715
735
|
# Return the results of an EXPLAIN query as a string
|
|
716
736
|
def explain(opts={})
|
|
717
|
-
with_sql((opts[:analyze] ? EXPLAIN_ANALYZE : EXPLAIN) + select_sql).map(QUERY_PLAN).join(
|
|
737
|
+
with_sql((opts[:analyze] ? EXPLAIN_ANALYZE : EXPLAIN) + select_sql).map(QUERY_PLAN).join(CRLF)
|
|
718
738
|
end
|
|
719
739
|
|
|
720
740
|
# Return a cloned dataset which will use FOR SHARE to lock returned rows.
|
|
@@ -767,7 +787,9 @@ module Sequel
|
|
|
767
787
|
return super if server_version < 80200
|
|
768
788
|
|
|
769
789
|
# postgresql 8.2 introduces support for multi-row insert
|
|
770
|
-
|
|
790
|
+
sql = LiteralString.new('VALUES ')
|
|
791
|
+
expression_list_append(sql, values.map{|r| Array(r)})
|
|
792
|
+
[insert_sql(columns, sql)]
|
|
771
793
|
end
|
|
772
794
|
|
|
773
795
|
# PostgreSQL supports using the WITH clause in subqueries if it
|
|
@@ -818,7 +840,8 @@ module Sequel
|
|
|
818
840
|
|
|
819
841
|
# Only include the primary table in the main delete clause
|
|
820
842
|
def delete_from_sql(sql)
|
|
821
|
-
sql <<
|
|
843
|
+
sql << FROM
|
|
844
|
+
source_list_append(sql, @opts[:from][0..0])
|
|
822
845
|
end
|
|
823
846
|
|
|
824
847
|
# Use USING to specify additional tables in a delete query
|
|
@@ -849,14 +872,15 @@ module Sequel
|
|
|
849
872
|
if(from = @opts[:from][1..-1]).empty?
|
|
850
873
|
raise(Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs') if @opts[:join]
|
|
851
874
|
else
|
|
852
|
-
sql <<
|
|
875
|
+
sql << SPACE << type.to_s << SPACE
|
|
876
|
+
source_list_append(sql, from)
|
|
853
877
|
select_join_sql(sql)
|
|
854
878
|
end
|
|
855
879
|
end
|
|
856
880
|
|
|
857
881
|
# Use a generic blob quoting method, hopefully overridden in one of the subadapter methods
|
|
858
|
-
def
|
|
859
|
-
|
|
882
|
+
def literal_blob_append(sql, v)
|
|
883
|
+
sql << APOS << v.gsub(BLOB_RE){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"} << APOS
|
|
860
884
|
end
|
|
861
885
|
|
|
862
886
|
# PostgreSQL uses FALSE for false values
|
|
@@ -865,8 +889,8 @@ module Sequel
|
|
|
865
889
|
end
|
|
866
890
|
|
|
867
891
|
# Assume that SQL standard quoting is on, per Sequel's defaults
|
|
868
|
-
def
|
|
869
|
-
|
|
892
|
+
def literal_string_append(sql, v)
|
|
893
|
+
sql << APOS << v.gsub(APOS_RE, DOUBLE_APOS) << APOS
|
|
870
894
|
end
|
|
871
895
|
|
|
872
896
|
# PostgreSQL uses FALSE for false values
|
|
@@ -881,8 +905,10 @@ module Sequel
|
|
|
881
905
|
|
|
882
906
|
# PostgreSQL requires parentheses around compound datasets if they use
|
|
883
907
|
# CTEs, and using them in other places doesn't hurt.
|
|
884
|
-
def
|
|
885
|
-
|
|
908
|
+
def compound_dataset_sql_append(sql, ds)
|
|
909
|
+
sql << PAREN_OPEN
|
|
910
|
+
super
|
|
911
|
+
sql << PAREN_CLOSE
|
|
886
912
|
end
|
|
887
913
|
|
|
888
914
|
# Support FOR SHARE locking when using the :share lock style.
|
|
@@ -892,14 +918,26 @@ module Sequel
|
|
|
892
918
|
|
|
893
919
|
# SQL fragment for named window specifications
|
|
894
920
|
def select_window_sql(sql)
|
|
895
|
-
|
|
921
|
+
if ws = @opts[:window]
|
|
922
|
+
sql << WINDOW
|
|
923
|
+
c = false
|
|
924
|
+
co = COMMA
|
|
925
|
+
as = AS
|
|
926
|
+
ws.map do |name, window|
|
|
927
|
+
sql << co if c
|
|
928
|
+
literal_append(sql, name)
|
|
929
|
+
sql << as
|
|
930
|
+
literal_append(sql, window)
|
|
931
|
+
c ||= true
|
|
932
|
+
end
|
|
933
|
+
end
|
|
896
934
|
end
|
|
897
935
|
|
|
898
936
|
# Use WITH RECURSIVE instead of WITH if any of the CTEs is recursive
|
|
899
937
|
def select_with_sql_base
|
|
900
938
|
opts[:with].any?{|w| w[:recursive]} ? SQL_WITH_RECURSIVE : super
|
|
901
939
|
end
|
|
902
|
-
|
|
940
|
+
|
|
903
941
|
# The version of the database server
|
|
904
942
|
def server_version
|
|
905
943
|
db.server_version(@opts[:server])
|
|
@@ -907,8 +945,8 @@ module Sequel
|
|
|
907
945
|
|
|
908
946
|
# Concatenate the expressions with a space in between
|
|
909
947
|
def full_text_string_join(cols)
|
|
910
|
-
cols = Array(cols).map{|x| SQL::Function.new(:COALESCE, x,
|
|
911
|
-
cols = cols.zip([
|
|
948
|
+
cols = Array(cols).map{|x| SQL::Function.new(:COALESCE, x, EMPTY_STRING)}
|
|
949
|
+
cols = cols.zip([SPACE] * cols.length).flatten
|
|
912
950
|
cols.pop
|
|
913
951
|
literal(SQL::StringExpression.new(:'||', *cols))
|
|
914
952
|
end
|
|
@@ -925,7 +963,8 @@ module Sequel
|
|
|
925
963
|
|
|
926
964
|
# Only include the primary table in the main update clause
|
|
927
965
|
def update_table_sql(sql)
|
|
928
|
-
sql <<
|
|
966
|
+
sql << SPACE
|
|
967
|
+
source_list_append(sql, @opts[:from][0..0])
|
|
929
968
|
end
|
|
930
969
|
end
|
|
931
970
|
end
|