sequel 4.39.0 → 4.40.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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +34 -0
  3. data/README.rdoc +8 -4
  4. data/doc/active_record.rdoc +1 -1
  5. data/doc/advanced_associations.rdoc +7 -7
  6. data/doc/association_basics.rdoc +7 -7
  7. data/doc/cheat_sheet.rdoc +5 -3
  8. data/doc/core_extensions.rdoc +3 -3
  9. data/doc/dataset_filtering.rdoc +1 -1
  10. data/doc/object_model.rdoc +16 -7
  11. data/doc/postgresql.rdoc +3 -3
  12. data/doc/querying.rdoc +3 -3
  13. data/doc/release_notes/4.40.0.txt +179 -0
  14. data/doc/security.rdoc +2 -1
  15. data/doc/sql.rdoc +34 -18
  16. data/doc/testing.rdoc +1 -0
  17. data/doc/virtual_rows.rdoc +11 -2
  18. data/lib/sequel/adapters/jdbc/derby.rb +7 -1
  19. data/lib/sequel/adapters/jdbc/h2.rb +15 -1
  20. data/lib/sequel/adapters/oracle.rb +9 -5
  21. data/lib/sequel/adapters/postgres.rb +0 -1
  22. data/lib/sequel/adapters/shared/cubrid.rb +11 -11
  23. data/lib/sequel/adapters/shared/db2.rb +4 -8
  24. data/lib/sequel/adapters/shared/mssql.rb +41 -28
  25. data/lib/sequel/adapters/shared/mysql.rb +9 -6
  26. data/lib/sequel/adapters/shared/oracle.rb +16 -5
  27. data/lib/sequel/adapters/shared/postgres.rb +84 -45
  28. data/lib/sequel/adapters/shared/sqlanywhere.rb +29 -15
  29. data/lib/sequel/adapters/shared/sqlite.rb +6 -6
  30. data/lib/sequel/core.rb +61 -10
  31. data/lib/sequel/database/connecting.rb +2 -1
  32. data/lib/sequel/database/features.rb +7 -0
  33. data/lib/sequel/database/query.rb +1 -1
  34. data/lib/sequel/database/schema_methods.rb +30 -3
  35. data/lib/sequel/database/transactions.rb +4 -2
  36. data/lib/sequel/dataset/actions.rb +1 -1
  37. data/lib/sequel/dataset/graph.rb +6 -1
  38. data/lib/sequel/dataset/query.rb +14 -7
  39. data/lib/sequel/dataset/sql.rb +2 -2
  40. data/lib/sequel/extensions/core_extensions.rb +2 -1
  41. data/lib/sequel/extensions/pg_row.rb +2 -2
  42. data/lib/sequel/extensions/s.rb +57 -0
  43. data/lib/sequel/extensions/set_overrides.rb +5 -1
  44. data/lib/sequel/extensions/sql_expr.rb +1 -0
  45. data/lib/sequel/extensions/symbol_aref.rb +71 -0
  46. data/lib/sequel/extensions/symbol_aref_refinement.rb +41 -0
  47. data/lib/sequel/extensions/symbol_as.rb +23 -0
  48. data/lib/sequel/extensions/symbol_as_refinement.rb +35 -0
  49. data/lib/sequel/model/base.rb +3 -3
  50. data/lib/sequel/plugins/class_table_inheritance.rb +14 -3
  51. data/lib/sequel/plugins/column_select.rb +4 -2
  52. data/lib/sequel/plugins/dataset_associations.rb +12 -4
  53. data/lib/sequel/plugins/insert_returning_select.rb +1 -1
  54. data/lib/sequel/plugins/mssql_optimistic_locking.rb +1 -1
  55. data/lib/sequel/plugins/prepared_statements.rb +1 -0
  56. data/lib/sequel/sql.rb +40 -8
  57. data/lib/sequel/version.rb +1 -1
  58. data/spec/adapters/firebird_spec.rb +3 -3
  59. data/spec/adapters/mssql_spec.rb +40 -40
  60. data/spec/adapters/mysql_spec.rb +5 -5
  61. data/spec/adapters/oracle_spec.rb +4 -4
  62. data/spec/adapters/postgres_spec.rb +135 -124
  63. data/spec/adapters/spec_helper.rb +1 -0
  64. data/spec/adapters/sqlite_spec.rb +6 -6
  65. data/spec/core/dataset_spec.rb +2 -2
  66. data/spec/core/expression_filters_spec.rb +43 -2
  67. data/spec/core/schema_spec.rb +35 -1
  68. data/spec/core_extensions_spec.rb +27 -0
  69. data/spec/extensions/class_table_inheritance_spec.rb +8 -0
  70. data/spec/extensions/column_select_spec.rb +8 -0
  71. data/spec/extensions/core_refinements_spec.rb +1 -1
  72. data/spec/extensions/dataset_associations_spec.rb +9 -0
  73. data/spec/extensions/insert_returning_select_spec.rb +20 -0
  74. data/spec/extensions/prepared_statements_spec.rb +7 -0
  75. data/spec/extensions/s_spec.rb +60 -0
  76. data/spec/extensions/symbol_aref_refinement_spec.rb +28 -0
  77. data/spec/extensions/symbol_as_refinement_spec.rb +21 -0
  78. data/spec/integration/associations_test.rb +62 -57
  79. data/spec/integration/dataset_test.rb +54 -54
  80. data/spec/integration/eager_loader_test.rb +7 -7
  81. data/spec/integration/plugin_test.rb +20 -20
  82. data/spec/integration/prepared_statement_test.rb +1 -1
  83. data/spec/integration/schema_test.rb +21 -0
  84. data/spec/integration/spec_helper.rb +1 -0
  85. metadata +12 -2
@@ -4,10 +4,6 @@ Sequel.require 'adapters/utils/split_alter_table'
4
4
  Sequel.require 'adapters/utils/replace'
5
5
 
6
6
  module Sequel
7
- Dataset::NON_SQL_OPTIONS << :insert_ignore
8
- Dataset::NON_SQL_OPTIONS << :update_ignore
9
- Dataset::NON_SQL_OPTIONS << :on_duplicate_key_update
10
-
11
7
  module MySQL
12
8
  Sequel::Database.set_shared_adapter_scheme(:mysql, self)
13
9
 
@@ -76,11 +72,11 @@ module Sequel
76
72
  m = output_identifier_meth
77
73
  im = input_identifier_meth
78
74
  ds = metadata_dataset.
79
- from(:INFORMATION_SCHEMA__KEY_COLUMN_USAGE).
75
+ from(Sequel[:INFORMATION_SCHEMA][:KEY_COLUMN_USAGE]).
80
76
  where(:TABLE_NAME=>im.call(table), :TABLE_SCHEMA=>Sequel.function(:DATABASE)).
81
77
  exclude(:CONSTRAINT_NAME=>'PRIMARY').
82
78
  exclude(:REFERENCED_TABLE_NAME=>nil).
83
- select(:CONSTRAINT_NAME___name, :COLUMN_NAME___column, :REFERENCED_TABLE_NAME___table, :REFERENCED_COLUMN_NAME___key)
79
+ select(Sequel[:CONSTRAINT_NAME].as(:name), Sequel[:COLUMN_NAME].as(:column), Sequel[:REFERENCED_TABLE_NAME].as(:table), Sequel[:REFERENCED_COLUMN_NAME].as(:key))
84
80
 
85
81
  h = {}
86
82
  ds.each do |row|
@@ -623,6 +619,8 @@ module Sequel
623
619
  # Comes directly from MySQL's documentation, used for queries with limits without offsets
624
620
  ONLY_OFFSET = ",18446744073709551615".freeze
625
621
 
622
+ NON_SQL_OPTIONS = (Dataset::NON_SQL_OPTIONS + [:insert_ignore, :update_ignore, :on_duplicate_key_update]).freeze
623
+
626
624
  Dataset.def_sql_method(self, :delete, %w'delete from where order limit')
627
625
  Dataset.def_sql_method(self, :insert, %w'insert ignore into columns values on_duplicate_key_update')
628
626
  Dataset.def_sql_method(self, :select, %w'select distinct calc_found_rows columns from join where group having compounds order limit lock')
@@ -998,6 +996,11 @@ module Sequel
998
996
  :values
999
997
  end
1000
998
 
999
+ # Dataset options that do not affect the generated SQL.
1000
+ def non_sql_options
1001
+ NON_SQL_OPTIONS
1002
+ end
1003
+
1001
1004
  def select_only_offset_sql(sql)
1002
1005
  sql << LIMIT
1003
1006
  literal_append(sql, @opts[:offset])
@@ -47,11 +47,22 @@ module Sequel
47
47
  im = input_identifier_meth
48
48
  schema, table = schema_and_table(table)
49
49
  ds = metadata_dataset.
50
- from(:all_cons_columns___pc, :all_constraints___p, :all_cons_columns___fc, :all_constraints___f).
51
- where(:f__table_name=>im.call(table), :f__constraint_type=>'R', :p__owner=>:f__r_owner, :p__constraint_name=>:f__r_constraint_name, :pc__owner=>:p__owner, :pc__constraint_name=>:p__constraint_name, :pc__table_name=>:p__table_name, :fc__owner=>:f__owner, :fc__constraint_name=>:f__constraint_name, :fc__table_name=>:f__table_name, :fc__position=>:pc__position).
52
- select(:p__table_name___table, :pc__column_name___key, :fc__column_name___column, :f__constraint_name___name).
53
- order(:table, :fc__position)
54
- ds = ds.where(:f__schema_name=>im.call(schema)) if schema
50
+ from{[all_cons_columns.as(:pc), all_constraints.as(:p), all_cons_columns.as(:fc), all_constraints.as(:f)]}.
51
+ where{{
52
+ f[:table_name]=>im.call(table),
53
+ f[:constraint_type]=>'R',
54
+ p[:owner]=>f[:r_owner],
55
+ p[:constraint_name]=>f[:r_constraint_name],
56
+ pc[:owner]=>p[:owner],
57
+ pc[:constraint_name]=>p[:constraint_name],
58
+ pc[:table_name]=>p[:table_name],
59
+ fc[:owner]=>f[:owner],
60
+ fc[:constraint_name]=>f[:constraint_name],
61
+ fc[:table_name]=>f[:table_name],
62
+ fc[:position]=>pc[:position]}}.
63
+ select{[p[:table_name].as(:table), pc[:column_name].as(:key), fc[:column_name].as(:column), f[:constraint_name].as(:name)]}.
64
+ order{[:table, fc[:position]]}
65
+ ds = ds.where{{f[:schema_name]=>im.call(schema)}} if schema
55
66
 
56
67
  fks = {}
57
68
  ds.each do |r|
@@ -309,11 +309,15 @@ module Sequel
309
309
  m = output_identifier_meth
310
310
  schema, _ = opts.fetch(:schema, schema_and_table(table))
311
311
  range = 0...32
312
+ oid = regclass_oid(table)
312
313
 
313
314
  base_ds = metadata_dataset.
314
- from(:pg_constraint___co).
315
- join(:pg_class___cl, :oid=>:conrelid).
316
- where(:cl__relkind=>'r', :co__contype=>'f', :cl__oid=>regclass_oid(table))
315
+ from{pg_constraint.as(:co)}.
316
+ join(Sequel[:pg_class].as(:cl), :oid=>:conrelid).
317
+ where{{
318
+ cl[:relkind]=>'r',
319
+ co[:contype]=>'f',
320
+ cl[:oid]=>oid}}
317
321
 
318
322
  # We split the parsing into two separate queries, which are merged manually later.
319
323
  # This is because PostgreSQL stores both the referencing and referenced columns in
@@ -322,22 +326,33 @@ module Sequel
322
326
  # the index of that element in the array.
323
327
 
324
328
  ds = base_ds.
325
- join(:pg_attribute___att, :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, :co__conkey)).
326
- order(:co__conname, SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(:co__conkey, [x]), x]}, 32, :att__attnum)).
327
- select(:co__conname___name, :att__attname___column, :co__confupdtype___on_update, :co__confdeltype___on_delete,
328
- SQL::BooleanExpression.new(:AND, :co__condeferrable, :co__condeferred).as(:deferrable))
329
+ join(Sequel[:pg_attribute].as(:att), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:conkey])).
330
+ order{[
331
+ co[:conname],
332
+ SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:conkey], [x]), x]}, 32, att[:attnum])]}.
333
+ select{[
334
+ co[:conname].as(:name),
335
+ att[:attname].as(:column),
336
+ co[:confupdtype].as(:on_update),
337
+ co[:confdeltype].as(:on_delete),
338
+ SQL::BooleanExpression.new(:AND, co[:condeferrable], co[:condeferred]).as(:deferrable)]}
329
339
 
330
340
  ref_ds = base_ds.
331
- join(:pg_class___cl2, :oid=>:co__confrelid).
332
- join(:pg_attribute___att2, :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, :co__confkey)).
333
- order(:co__conname, SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(:co__confkey, [x]), x]}, 32, :att2__attnum)).
334
- select(:co__conname___name, :cl2__relname___table, :att2__attname___refcolumn)
341
+ join(Sequel[:pg_class].as(:cl2), :oid=>Sequel[:co][:confrelid]).
342
+ join(Sequel[:pg_attribute].as(:att2), :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, Sequel[:co][:confkey])).
343
+ order{[
344
+ co[:conname],
345
+ SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:confkey], [x]), x]}, 32, att2[:attnum])]}.
346
+ select{[
347
+ co[:conname].as(:name),
348
+ cl2[:relname].as(:table),
349
+ att2[:attname].as(:refcolumn)]}
335
350
 
336
351
  # If a schema is given, we only search in that schema, and the returned :table
337
352
  # entry is schema qualified as well.
338
353
  if schema
339
- ref_ds = ref_ds.join(:pg_namespace___nsp2, :oid=>:cl2__relnamespace).
340
- select_more(:nsp2__nspname___schema)
354
+ ref_ds = ref_ds.join(Sequel[:pg_namespace].as(:nsp2), :oid=>Sequel[:cl2][:relnamespace]).
355
+ select_more{nsp2[:nspname].as(:schema)}
341
356
  end
342
357
 
343
358
  h = {}
@@ -362,18 +377,25 @@ module Sequel
362
377
  def indexes(table, opts=OPTS)
363
378
  m = output_identifier_meth
364
379
  range = 0...32
365
- attnums = server_version >= 80100 ? SQL::Function.new(:ANY, :ind__indkey) : range.map{|x| SQL::Subscript.new(:ind__indkey, [x])}
380
+ attnums = server_version >= 80100 ? SQL::Function.new(:ANY, Sequel[:ind][:indkey]) : range.map{|x| SQL::Subscript.new(Sequel[:ind][:indkey], [x])}
381
+ oid = regclass_oid(table, opts)
366
382
  ds = metadata_dataset.
367
- from(:pg_class___tab).
368
- join(:pg_index___ind, :indrelid=>:oid).
369
- join(:pg_class___indc, :oid=>:indexrelid).
370
- join(:pg_attribute___att, :attrelid=>:tab__oid, :attnum=>attnums).
371
- left_join(:pg_constraint___con, :conname=>:indc__relname).
372
- filter(:indc__relkind=>'i', :ind__indisprimary=>false, :indexprs=>nil, :indpred=>nil, :indisvalid=>true, :tab__oid=>regclass_oid(table, opts)).
373
- order(:indc__relname, SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(:ind__indkey, [x]), x]}, 32, :att__attnum)).
374
- select(:indc__relname___name, :ind__indisunique___unique, :att__attname___column, :con__condeferrable___deferrable)
375
-
376
- ds.filter!(:indisready=>true, :indcheckxmin=>false) if server_version >= 80300
383
+ from{pg_class.as(:tab)}.
384
+ join(Sequel[:pg_index].as(:ind), :indrelid=>:oid).
385
+ join(Sequel[:pg_class].as(:indc), :oid=>:indexrelid).
386
+ join(Sequel[:pg_attribute].as(:att), :attrelid=>Sequel[:tab][:oid], :attnum=>attnums).
387
+ left_join(Sequel[:pg_constraint].as(:con), :conname=>Sequel[:indc][:relname]).
388
+ where{{
389
+ indc[:relkind]=>'i',
390
+ ind[:indisprimary]=>false,
391
+ :indexprs=>nil,
392
+ :indpred=>nil,
393
+ :indisvalid=>true,
394
+ tab[:oid]=>oid}}.
395
+ order{[indc[:relname], SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(ind[:indkey], [x]), x]}, 32, att[:attnum])]}.
396
+ select{[indc[:relname].as(:name), ind[:indisunique].as(:unique), att[:attname].as(:column), con[:condeferrable].as(:deferrable)]}
397
+
398
+ ds = ds.where(:indisready=>true, :indcheckxmin=>false) if server_version >= 80300
377
399
 
378
400
  indexes = {}
379
401
  ds.each do |r|
@@ -385,7 +407,7 @@ module Sequel
385
407
 
386
408
  # Dataset containing all current database locks
387
409
  def locks
388
- dataset.from(:pg_class).join(:pg_locks, :relation=>:relfilenode).select(:pg_class__relname, Sequel::SQL::ColumnAll.new(:pg_locks))
410
+ dataset.from(:pg_class).join(:pg_locks, :relation=>:relfilenode).select{[pg_class[:relname], Sequel::SQL::ColumnAll.new(:pg_locks)]}
389
411
  end
390
412
 
391
413
  # Notifies the given channel. See the PostgreSQL NOTIFY documentation. Options:
@@ -972,7 +994,7 @@ module Sequel
972
994
  else
973
995
  Sequel.function(:any, Sequel.function(:current_schemas, false))
974
996
  end
975
- ds.where(:pg_namespace__nspname=>expr)
997
+ ds.where{{pg_namespace[:nspname]=>expr}}
976
998
  end
977
999
 
978
1000
  # Return a hash with oid keys and callable values, used for converting types.
@@ -1032,7 +1054,7 @@ module Sequel
1032
1054
  if block_given?
1033
1055
  yield(ds)
1034
1056
  elsif opts[:qualify]
1035
- ds.select_append(:pg_namespace__nspname).map{|r| Sequel.qualify(m.call(r[:nspname]), m.call(r[:relname]))}
1057
+ ds.select_append{pg_namespace[:nspname]}.map{|r| Sequel.qualify(m.call(r[:nspname]).to_s, m.call(r[:relname]).to_s)}
1036
1058
  else
1037
1059
  ds.map{|r| m.call(r[:relname])}
1038
1060
  end
@@ -1098,24 +1120,26 @@ module Sequel
1098
1120
  # The dataset used for parsing table schemas, using the pg_* system catalogs.
1099
1121
  def schema_parse_table(table_name, opts)
1100
1122
  m = output_identifier_meth(opts[:dataset])
1101
- ds = metadata_dataset.select(:pg_attribute__attname___name,
1102
- SQL::Cast.new(:pg_attribute__atttypid, :integer).as(:oid),
1103
- SQL::Cast.new(:basetype__oid, :integer).as(:base_oid),
1104
- SQL::Function.new(:format_type, :basetype__oid, :pg_type__typtypmod).as(:db_base_type),
1105
- SQL::Function.new(:format_type, :pg_type__oid, :pg_attribute__atttypmod).as(:db_type),
1106
- SQL::Function.new(:pg_get_expr, :pg_attrdef__adbin, :pg_class__oid).as(:default),
1107
- SQL::BooleanExpression.new(:NOT, :pg_attribute__attnotnull).as(:allow_null),
1108
- SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(:pg_attribute__attnum => SQL::Function.new(:ANY, :pg_index__indkey)), false).as(:primary_key)).
1123
+ oid = regclass_oid(table_name, opts)
1124
+ ds = metadata_dataset.select{[
1125
+ pg_attribute[:attname].as(:name),
1126
+ SQL::Cast.new(pg_attribute[:atttypid], :integer).as(:oid),
1127
+ SQL::Cast.new(basetype[:oid], :integer).as(:base_oid),
1128
+ SQL::Function.new(:format_type, basetype[:oid], pg_type[:typtypmod]).as(:db_base_type),
1129
+ SQL::Function.new(:format_type, pg_type[:oid], pg_attribute[:atttypmod]).as(:db_type),
1130
+ SQL::Function.new(:pg_get_expr, pg_attrdef[:adbin], pg_class[:oid]).as(:default),
1131
+ SQL::BooleanExpression.new(:NOT, pg_attribute[:attnotnull]).as(:allow_null),
1132
+ SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(pg_attribute[:attnum] => SQL::Function.new(:ANY, pg_index[:indkey])), false).as(:primary_key)]}.
1109
1133
  from(:pg_class).
1110
1134
  join(:pg_attribute, :attrelid=>:oid).
1111
1135
  join(:pg_type, :oid=>:atttypid).
1112
- left_outer_join(:pg_type___basetype, :oid=>:typbasetype).
1113
- left_outer_join(:pg_attrdef, :adrelid=>:pg_class__oid, :adnum=>:pg_attribute__attnum).
1114
- left_outer_join(:pg_index, :indrelid=>:pg_class__oid, :indisprimary=>true).
1115
- filter(:pg_attribute__attisdropped=>false).
1116
- filter{|o| o.pg_attribute__attnum > 0}.
1117
- filter(:pg_class__oid=>regclass_oid(table_name, opts)).
1118
- order(:pg_attribute__attnum)
1136
+ left_outer_join(Sequel[:pg_type].as(:basetype), :oid=>:typbasetype).
1137
+ left_outer_join(:pg_attrdef, :adrelid=>Sequel[:pg_class][:oid], :adnum=>Sequel[:pg_attribute][:attnum]).
1138
+ left_outer_join(:pg_index, :indrelid=>Sequel[:pg_class][:oid], :indisprimary=>true).
1139
+ where{{pg_attribute[:attisdropped]=>false}}.
1140
+ where{pg_attribute[:attnum] > 0}.
1141
+ where{{pg_class[:oid]=>oid}}.
1142
+ order{pg_attribute[:attnum]}
1119
1143
  ds.map do |row|
1120
1144
  row[:default] = nil if blank_object?(row[:default])
1121
1145
  if row[:base_oid]
@@ -1240,6 +1264,8 @@ module Sequel
1240
1264
  LOCK_MODES = ['ACCESS SHARE', 'ROW SHARE', 'ROW EXCLUSIVE', 'SHARE UPDATE EXCLUSIVE', 'SHARE', 'SHARE ROW EXCLUSIVE', 'EXCLUSIVE', 'ACCESS EXCLUSIVE'].each(&:freeze)
1241
1265
  SKIP_LOCKED = " SKIP LOCKED".freeze
1242
1266
 
1267
+ NON_SQL_OPTIONS = (Dataset::NON_SQL_OPTIONS + [:cursor, :insert_conflict]).freeze
1268
+
1243
1269
  Dataset.def_sql_method(self, :delete, [['if server_version >= 90100', %w'with delete from using where returning'], ['else', %w'delete from using where returning']])
1244
1270
  Dataset.def_sql_method(self, :insert, [['if server_version >= 90500', %w'with insert into columns values conflict returning'], ['elsif server_version >= 90100', %w'with insert into columns values returning'], ['else', %w'insert into columns values returning']])
1245
1271
  Dataset.def_sql_method(self, :select, [['if opts[:values]', %w'values order limit'], ['elsif server_version >= 80400', %w'with select distinct columns from join where group having window compounds order limit lock'], ['else', %w'select distinct columns from join where group having compounds order limit lock']])
@@ -1403,6 +1429,7 @@ module Sequel
1403
1429
 
1404
1430
  # Handle uniqueness violations when inserting, by updating the conflicting row, using
1405
1431
  # ON CONFLICT. With no options, uses ON CONFLICT DO NOTHING. Options:
1432
+ # :conflict_where :: The index filter, when using a partial index to determine uniqueness.
1406
1433
  # :constraint :: An explicit constraint name, has precendence over :target.
1407
1434
  # :target :: The column name or expression to handle uniqueness violations on.
1408
1435
  # :update :: A hash of columns and values to set. Uses ON CONFLICT DO UPDATE.
@@ -1421,6 +1448,10 @@ module Sequel
1421
1448
  # DB[:table].insert_conflict(:target=>:a).insert(:a=>1, :b=>2)
1422
1449
  # # INSERT INTO TABLE (a, b) VALUES (1, 2)
1423
1450
  # # ON CONFLICT (a) DO NOTHING
1451
+ #
1452
+ # DB[:table].insert_conflict(:target=>:a, :conflict_where=>{:c=>true}).insert(:a=>1, :b=>2)
1453
+ # # INSERT INTO TABLE (a, b) VALUES (1, 2)
1454
+ # # ON CONFLICT (a) WHERE (c IS TRUE) DO NOTHING
1424
1455
  #
1425
1456
  # DB[:table].insert_conflict(:target=>:a, :update=>{:b=>:excluded__b}).insert(:a=>1, :b=>2)
1426
1457
  # # INSERT INTO TABLE (a, b) VALUES (1, 2)
@@ -1642,19 +1673,22 @@ module Sequel
1642
1673
  elsif target = opts[:target]
1643
1674
  sql << ' '
1644
1675
  identifier_append(sql, Array(target))
1676
+ if conflict_where = opts[:conflict_where]
1677
+ sql << " WHERE "
1678
+ literal_append(sql, conflict_where)
1679
+ end
1645
1680
  end
1646
1681
 
1647
1682
  if values = opts[:update]
1648
1683
  sql << " DO UPDATE SET "
1649
1684
  update_sql_values_hash(sql, values)
1650
- if where = opts[:update_where]
1685
+ if update_where = opts[:update_where]
1651
1686
  sql << " WHERE "
1652
- literal_append(sql, where)
1687
+ literal_append(sql, update_where)
1653
1688
  end
1654
1689
  else
1655
1690
  sql << " DO NOTHING"
1656
1691
  end
1657
-
1658
1692
  end
1659
1693
  end
1660
1694
 
@@ -1720,6 +1754,11 @@ module Sequel
1720
1754
  :values
1721
1755
  end
1722
1756
 
1757
+ # Dataset options that do not affect the generated SQL.
1758
+ def non_sql_options
1759
+ NON_SQL_OPTIONS
1760
+ end
1761
+
1723
1762
  # PostgreSQL requires parentheses around compound datasets if they use
1724
1763
  # CTEs, and using them in other places doesn't hurt.
1725
1764
  def compound_dataset_sql_append(sql, ds)
@@ -57,8 +57,8 @@ module Sequel
57
57
  im = input_identifier_meth(opts[:dataset])
58
58
  metadata_dataset.
59
59
  from{sa_describe_query("select * from #{im.call(table)}").as(:a)}.
60
- join(:syscolumn___b, :table_id=>:base_table_id, :column_id=>:base_column_id).
61
- order(:a__column_number).
60
+ join(Sequel[:syscolumn].as(:b), :table_id=>:base_table_id, :column_id=>:base_column_id).
61
+ order{a[:column_number]}.
62
62
  map do |row|
63
63
  auto_increment = row.delete(:is_autoincrement)
64
64
  row[:auto_increment] = auto_increment == 1 || auto_increment == true
@@ -80,11 +80,15 @@ module Sequel
80
80
  im = input_identifier_meth
81
81
  indexes = {}
82
82
  metadata_dataset.
83
- from(:dbo__sysobjects___z).
84
- select(:z__name___table_name, :i__name___index_name, :si__indextype___type, :si__colnames___columns).
85
- join(:dbo__sysindexes___i, :id___i=> :id___z).
86
- join(:sys__sysindexes___si, :iname=> :name___i).
87
- where(:z__type => 'U', :table_name=>im.call(table)).
83
+ from(Sequel[:dbo][:sysobjects].as(:z)).
84
+ select{[
85
+ z[:name].as(:table_name),
86
+ i[:name].as(:index_name),
87
+ si[:indextype].as(:type),
88
+ si[:colnames].as(:columns)]}.
89
+ join(Sequel[:dbo][:sysindexes].as(:i), :id=>:id).
90
+ join(Sequel[:sys][:sysindexes].as(:si), :iname=> :name).
91
+ where{{z[:type] => 'U', :table_name=>im.call(table)}}.
88
92
  each do |r|
89
93
  indexes[m.call(r[:index_name])] =
90
94
  {:unique=>(r[:type].downcase=='unique'),
@@ -98,11 +102,16 @@ module Sequel
98
102
  im = input_identifier_meth
99
103
  fk_indexes = {}
100
104
  metadata_dataset.
101
- from(:sys__sysforeignkey___fk).
102
- select(:fk__role___name, :fks__columns___column_map, :si__indextype___type, :si__colnames___columns, :fks__primary_tname___table_name).
103
- join(:sys__sysforeignkeys___fks, :role => :role).
104
- join_table(:inner, :sys__sysindexes___si, [:iname=> :fk__role], {:implicit_qualifier => :fk}).
105
- where(:fks__foreign_tname=>im.call(table)).
105
+ from{sys[:sysforeignkey].as(:fk)}.
106
+ select{[
107
+ fk[:role].as(:name),
108
+ fks[:columns].as(:column_map),
109
+ si[:indextype].as(:type),
110
+ si[:colnames].as(:columns),
111
+ fks[:primary_tname].as(:table_name)]}.
112
+ join(Sequel[:sys][:sysforeignkeys].as(:fks), :role => :role).
113
+ join(Sequel[:sys][:sysindexes].as(:si), [:iname=> Sequel[:fk][:role]], {:implicit_qualifier => :fk}).
114
+ where{{fks[:foreign_tname]=>im.call(table)}}.
106
115
  each do |r|
107
116
  unless r[:type].downcase == 'primary key'
108
117
  fk_indexes[r[:name]] =
@@ -142,6 +151,11 @@ module Sequel
142
151
  AUTO_INCREMENT
143
152
  end
144
153
 
154
+ # Sybase does not allow adding primary key constraints to NULLable columns.
155
+ def can_add_primary_key_constraint_on_nullable_columns?
156
+ false
157
+ end
158
+
145
159
  # SQL fragment for marking a table as temporary
146
160
  def temporary_table_sql
147
161
  TEMPORARY
@@ -235,9 +249,9 @@ module Sequel
235
249
  def tables_and_views(type, opts=OPTS)
236
250
  m = output_identifier_meth
237
251
  metadata_dataset.
238
- from(:sysobjects___a).
239
- where(:a__type=>type).
240
- select_map(:a__name).
252
+ from{sysobjects.as(:a)}.
253
+ where{{a[:type]=>type}}.
254
+ select_map{a[:name]}.
241
255
  map{|n| m.call(n)}
242
256
  end
243
257
 
@@ -314,17 +314,17 @@ module Sequel
314
314
  end
315
315
  end
316
316
 
317
+ # SQLite allows adding primary key constraints on NULLABLE columns, but then
318
+ # does not enforce NOT NULL for such columns, so force setting the columns NOT NULL.
319
+ def can_add_primary_key_constraint_on_nullable_columns?
320
+ false
321
+ end
322
+
317
323
  # Surround default with parens to appease SQLite
318
324
  def column_definition_default_sql(sql, column)
319
325
  sql << " DEFAULT (#{literal(column[:default])})" if column.include?(:default)
320
326
  end
321
327
 
322
- # Add null/not null SQL fragment to column creation SQL.
323
- def column_definition_null_sql(sql, column)
324
- column = column.merge(:null=>false) if column[:primary_key]
325
- super(sql, column)
326
- end
327
-
328
328
  # Array of PRAGMA SQL statements based on the Database options that should be applied to
329
329
  # new connections.
330
330
  def connection_pragmas
@@ -24,6 +24,7 @@
24
24
  module Sequel
25
25
  @convert_two_digit_years = true
26
26
  @datetime_class = Time
27
+ @split_symbols = true
27
28
 
28
29
  # Whether Sequel is being run in single threaded mode
29
30
  @single_threaded = false
@@ -234,28 +235,78 @@ module Sequel
234
235
  COLUMN_REF_RE3 = /\A((?:(?!__).)+)__(.+)\z/.freeze
235
236
  SPLIT_SYMBOL_CACHE = {}
236
237
 
237
- # Splits the symbol into three parts. Each part will
238
- # either be a string or nil.
238
+ # Splits the symbol into three parts, if symbol splitting is enabled.
239
+ # Each part will either be a string or nil. If symbol splitting
240
+ # is disabled, returns an array with the first and third parts
241
+ # being nil, and the second part beind a string version of the symbol.
239
242
  #
240
243
  # For columns, these parts are the table, column, and alias.
241
244
  # For tables, these parts are the schema, table, and alias.
242
245
  def self.split_symbol(sym)
243
246
  unless v = Sequel.synchronize{SPLIT_SYMBOL_CACHE[sym]}
244
- v = case s = sym.to_s
245
- when COLUMN_REF_RE1
246
- [$1.freeze, $2.freeze, $3.freeze].freeze
247
- when COLUMN_REF_RE2
248
- [nil, $1.freeze, $2.freeze].freeze
249
- when COLUMN_REF_RE3
250
- [$1.freeze, $2.freeze, nil].freeze
247
+ if split_symbols?
248
+ v = case s = sym.to_s
249
+ when COLUMN_REF_RE1
250
+ [$1.freeze, $2.freeze, $3.freeze].freeze
251
+ when COLUMN_REF_RE2
252
+ [nil, $1.freeze, $2.freeze].freeze
253
+ when COLUMN_REF_RE3
254
+ [$1.freeze, $2.freeze, nil].freeze
255
+ else
256
+ [nil, s.freeze, nil].freeze
257
+ end
251
258
  else
252
- [nil, s.freeze, nil].freeze
259
+ v = [nil,sym.to_s.freeze,nil].freeze
253
260
  end
254
261
  Sequel.synchronize{SPLIT_SYMBOL_CACHE[sym] = v}
255
262
  end
256
263
  v
257
264
  end
258
265
 
266
+ # Sequel by default will split symbols, treating:
267
+ #
268
+ # :table__column # table.column
269
+ # :column___alias # column AS alias
270
+ # :table__column___alias # table.column AS alias
271
+ #
272
+ # This can cause problems if any identifiers in the database use a double
273
+ # or triple underscore. When Sequel was first created, using symbols with
274
+ # double or triple underscores was the only way to represent qualified or
275
+ # aliased identifiers. Sequel now offers many ways to create qualified and
276
+ # aliased identifiers, so there is less of a need for this now. This allows
277
+ # you to turn off symbol splitting, potentially avoiding problems if you
278
+ # have identifiers that use double underscores:
279
+ #
280
+ # Sequel.split_symbols = false
281
+ #
282
+ # Note that Sequel::Database instances do their own caching of literalized
283
+ # symbols, and changing this setting does not affect those caches. It is
284
+ # recommended that if you want to change this setting, you do so directly
285
+ # after requiring Sequel, before creating any Sequel::Database instances.
286
+ #
287
+ # Also note that disabling symbol splitting will also disable the handling
288
+ # of double underscores in virtual row methods, causing such methods to
289
+ # yield regular identifers instead of qualified identifiers. To make sure
290
+ # the code works when splitting symbols is both disabled and enabled, you
291
+ # can use Sequel::SQL::Identifier#[].
292
+ #
293
+ # # Sequel.split_symbols = true
294
+ # Sequel.expr{table__column} # table.column
295
+ # Sequel.expr{table[:column]} # table.column
296
+ #
297
+ # # Sequel.split_symbols = false
298
+ # Sequel.expr{table__column} # table__column
299
+ # Sequel.expr{table[:column]} # table.column
300
+ def self.split_symbols=(v)
301
+ Sequel.synchronize{SPLIT_SYMBOL_CACHE.clear}
302
+ @split_symbols = v
303
+ end
304
+
305
+ # Whether Sequel currently splits symbols into qualified/aliased identifiers.
306
+ def self.split_symbols?
307
+ @split_symbols
308
+ end
309
+
259
310
  # Converts the given +string+ into a +Date+ object.
260
311
  #
261
312
  # Sequel.string_to_date('2010-09-10') # Date.civil(2010, 09, 10)