sequel 4.12.0 → 4.13.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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +64 -0
  3. data/Rakefile +3 -1
  4. data/bin/sequel +13 -5
  5. data/doc/release_notes/4.13.0.txt +169 -0
  6. data/doc/sql.rdoc +3 -3
  7. data/lib/sequel/adapters/do.rb +11 -23
  8. data/lib/sequel/adapters/do/mysql.rb +8 -0
  9. data/lib/sequel/adapters/do/postgres.rb +8 -0
  10. data/lib/sequel/adapters/do/{sqlite.rb → sqlite3.rb} +9 -0
  11. data/lib/sequel/adapters/jdbc.rb +16 -139
  12. data/lib/sequel/adapters/jdbc/as400.rb +9 -0
  13. data/lib/sequel/adapters/jdbc/cubrid.rb +9 -0
  14. data/lib/sequel/adapters/jdbc/db2.rb +9 -0
  15. data/lib/sequel/adapters/jdbc/derby.rb +9 -0
  16. data/lib/sequel/adapters/jdbc/{firebird.rb → firebirdsql.rb} +9 -0
  17. data/lib/sequel/adapters/jdbc/h2.rb +10 -0
  18. data/lib/sequel/adapters/jdbc/hsqldb.rb +9 -0
  19. data/lib/sequel/adapters/jdbc/{informix.rb → informix-sqli.rb} +9 -0
  20. data/lib/sequel/adapters/jdbc/{progress.rb → jdbcprogress.rb} +9 -0
  21. data/lib/sequel/adapters/jdbc/jtds.rb +10 -0
  22. data/lib/sequel/adapters/jdbc/mysql.rb +14 -0
  23. data/lib/sequel/adapters/jdbc/oracle.rb +9 -0
  24. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -0
  25. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +23 -0
  26. data/lib/sequel/adapters/jdbc/sqlite.rb +10 -0
  27. data/lib/sequel/adapters/jdbc/sqlserver.rb +10 -0
  28. data/lib/sequel/adapters/odbc.rb +6 -14
  29. data/lib/sequel/adapters/odbc/db2.rb +9 -0
  30. data/lib/sequel/adapters/odbc/mssql.rb +8 -0
  31. data/lib/sequel/adapters/odbc/progress.rb +8 -0
  32. data/lib/sequel/adapters/oracle.rb +1 -1
  33. data/lib/sequel/adapters/postgres.rb +1 -1
  34. data/lib/sequel/adapters/shared/firebird.rb +8 -1
  35. data/lib/sequel/adapters/shared/mssql.rb +68 -27
  36. data/lib/sequel/adapters/shared/mysql.rb +3 -5
  37. data/lib/sequel/adapters/shared/oracle.rb +17 -3
  38. data/lib/sequel/adapters/shared/postgres.rb +9 -4
  39. data/lib/sequel/adapters/shared/sqlanywhere.rb +6 -6
  40. data/lib/sequel/database/connecting.rb +38 -17
  41. data/lib/sequel/dataset/actions.rb +6 -2
  42. data/lib/sequel/dataset/graph.rb +18 -20
  43. data/lib/sequel/dataset/misc.rb +37 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +1 -2
  45. data/lib/sequel/dataset/query.rb +1 -0
  46. data/lib/sequel/dataset/sql.rb +17 -10
  47. data/lib/sequel/extensions/dataset_source_alias.rb +90 -0
  48. data/lib/sequel/extensions/pg_array.rb +14 -10
  49. data/lib/sequel/extensions/pg_enum.rb +135 -0
  50. data/lib/sequel/extensions/pg_hstore.rb +4 -6
  51. data/lib/sequel/extensions/pg_inet.rb +4 -5
  52. data/lib/sequel/extensions/pg_interval.rb +3 -3
  53. data/lib/sequel/extensions/pg_json.rb +16 -12
  54. data/lib/sequel/extensions/pg_range.rb +5 -3
  55. data/lib/sequel/extensions/pg_row.rb +2 -2
  56. data/lib/sequel/extensions/round_timestamps.rb +52 -0
  57. data/lib/sequel/model.rb +5 -2
  58. data/lib/sequel/model/associations.rb +29 -3
  59. data/lib/sequel/model/base.rb +68 -29
  60. data/lib/sequel/plugins/class_table_inheritance.rb +25 -16
  61. data/lib/sequel/plugins/column_select.rb +57 -0
  62. data/lib/sequel/plugins/composition.rb +14 -16
  63. data/lib/sequel/plugins/dirty.rb +9 -11
  64. data/lib/sequel/plugins/insert_returning_select.rb +70 -0
  65. data/lib/sequel/plugins/instance_filters.rb +7 -9
  66. data/lib/sequel/plugins/lazy_attributes.rb +16 -4
  67. data/lib/sequel/plugins/list.rb +9 -0
  68. data/lib/sequel/plugins/modification_detection.rb +90 -0
  69. data/lib/sequel/plugins/serialization.rb +13 -15
  70. data/lib/sequel/plugins/serialization_modification_detection.rb +9 -9
  71. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  72. data/lib/sequel/plugins/timestamps.rb +6 -6
  73. data/lib/sequel/version.rb +1 -1
  74. data/spec/adapters/mysql_spec.rb +7 -0
  75. data/spec/adapters/postgres_spec.rb +41 -0
  76. data/spec/bin_spec.rb +4 -1
  77. data/spec/core/database_spec.rb +6 -0
  78. data/spec/core/dataset_spec.rb +100 -90
  79. data/spec/core/object_graph_spec.rb +5 -0
  80. data/spec/extensions/class_table_inheritance_spec.rb +18 -13
  81. data/spec/extensions/column_select_spec.rb +108 -0
  82. data/spec/extensions/composition_spec.rb +20 -0
  83. data/spec/extensions/dataset_source_alias_spec.rb +51 -0
  84. data/spec/extensions/insert_returning_select_spec.rb +46 -0
  85. data/spec/extensions/lazy_attributes_spec.rb +24 -20
  86. data/spec/extensions/list_spec.rb +5 -0
  87. data/spec/extensions/modification_detection_spec.rb +80 -0
  88. data/spec/extensions/pg_enum_spec.rb +64 -0
  89. data/spec/extensions/pg_json_spec.rb +7 -13
  90. data/spec/extensions/prepared_statements_spec.rb +6 -4
  91. data/spec/extensions/round_timestamps_spec.rb +43 -0
  92. data/spec/extensions/serialization_modification_detection_spec.rb +10 -1
  93. data/spec/extensions/serialization_spec.rb +18 -0
  94. data/spec/extensions/single_table_inheritance_spec.rb +5 -0
  95. data/spec/extensions/timestamps_spec.rb +6 -0
  96. data/spec/integration/plugin_test.rb +14 -8
  97. data/spec/integration/prepared_statement_test.rb +12 -0
  98. data/spec/model/associations_spec.rb +24 -0
  99. data/spec/model/model_spec.rb +13 -3
  100. data/spec/model/record_spec.rb +24 -1
  101. metadata +22 -6
@@ -368,12 +368,10 @@ module Sequel
368
368
  generator.columns.each do |c|
369
369
  if t = c.delete(:table)
370
370
  same_table = t == name
371
- k = c[:key]
371
+ key = c[:key] || key_proc.call(t)
372
372
 
373
- key ||= key_proc.call(t)
374
-
375
- if same_table && !k.nil?
376
- generator.constraints.unshift(:type=>:unique, :columns=>Array(k))
373
+ if same_table && !key.nil?
374
+ generator.constraints.unshift(:type=>:unique, :columns=>Array(key))
377
375
  end
378
376
 
379
377
  generator.foreign_key([c[:name]], t, c.merge(:name=>c[:foreign_key_constraint_name], :type=>:foreign_key, :key=>key))
@@ -59,19 +59,33 @@ module Sequel
59
59
  false
60
60
  end
61
61
 
62
+ IGNORE_OWNERS = %w'APEX_040000 CTXSYS EXFSYS MDSYS OLAPSYS ORDDATA ORDSYS SYS SYSTEM XDB XDBMETADATA XDBPM XFILES WMSYS'
63
+
62
64
  def tables(opts=OPTS)
63
65
  m = output_identifier_meth
64
- metadata_dataset.from(:tabs).server(opts[:server]).select(:table_name).map{|r| m.call(r[:table_name])}
66
+ metadata_dataset.from(:all_tables).
67
+ server(opts[:server]).
68
+ where(:dropped=>'NO').
69
+ exclude(:owner=>IGNORE_OWNERS).
70
+ select(:table_name).
71
+ map{|r| m.call(r[:table_name])}
65
72
  end
66
73
 
67
74
  def views(opts=OPTS)
68
75
  m = output_identifier_meth
69
- metadata_dataset.from(:tab).server(opts[:server]).select(:tname).filter(:tabtype => 'VIEW').map{|r| m.call(r[:tname])}
76
+ metadata_dataset.from(:all_views).
77
+ server(opts[:server]).
78
+ exclude(:owner=>IGNORE_OWNERS).
79
+ select(:view_name).
80
+ map{|r| m.call(r[:view_name])}
70
81
  end
71
82
 
72
83
  def view_exists?(name)
73
84
  m = input_identifier_meth
74
- metadata_dataset.from(:tab).filter(:tname =>m.call(name), :tabtype => 'VIEW').count > 0
85
+ metadata_dataset.from(:all_views).
86
+ exclude(:owner=>IGNORE_OWNERS).
87
+ where(:view_name=>m.call(name)).
88
+ count > 0
75
89
  end
76
90
 
77
91
  # Oracle supports deferrable constraints.
@@ -1297,10 +1297,15 @@ module Sequel
1297
1297
  # Insert a record returning the record inserted. Always returns nil without
1298
1298
  # inserting a query if disable_insert_returning is used.
1299
1299
  def insert_select(*values)
1300
- unless @opts[:disable_insert_returning]
1301
- ds = opts[:returning] ? self : returning
1302
- ds.insert(*values){|r| return r}
1303
- end
1300
+ return unless supports_insert_select?
1301
+ with_sql_first(insert_select_sql(*values))
1302
+ end
1303
+
1304
+ # The SQL to use for an insert_select, adds a RETURNING clause to the insert
1305
+ # unless the RETURNING clause is already present.
1306
+ def insert_select_sql(*values)
1307
+ ds = opts[:returning] ? self : returning
1308
+ ds.insert_sql(*values)
1304
1309
  end
1305
1310
 
1306
1311
  # Locks all tables in the dataset's FROM clause (but not in JOINs) with
@@ -106,7 +106,7 @@ module Sequel
106
106
  :columns=>r[:columns].split(',').map{|v| m.call(v.split(' ').first)},
107
107
  :table=>m.call(r[:table_name]),
108
108
  :key=>r[:column_map].split(',').map{|v| m.call(v.split(' IS ').last)}}
109
- end
109
+ end
110
110
  end
111
111
  fk_indexes.values
112
112
  end
@@ -255,7 +255,6 @@ module Sequel
255
255
  DATEPART = 'datepart'.freeze
256
256
  REGEXP = 'REGEXP'.freeze
257
257
  NOT_REGEXP = 'NOT REGEXP'.freeze
258
- TIMESTAMP_USEC_FORMAT = ".%03d".freeze
259
258
  APOS = Dataset::APOS
260
259
  APOS_RE = Dataset::APOS_RE
261
260
  DOUBLE_APOS = Dataset::DOUBLE_APOS
@@ -425,10 +424,6 @@ module Sequel
425
424
  end
426
425
  end
427
426
 
428
- def format_timestamp_usec(usec)
429
- sprintf(TIMESTAMP_USEC_FORMAT, usec/1000)
430
- end
431
-
432
427
  # Sybase uses TOP N for limit. For Sybase TOP (N) is used
433
428
  # to allow the limit to be a bound variable.
434
429
  def select_limit_sql(sql)
@@ -465,6 +460,11 @@ module Sequel
465
460
  super
466
461
  end
467
462
  end
463
+
464
+ # SQLAnywhere supports millisecond timestamp precision.
465
+ def timestamp_precision
466
+ 3
467
+ end
468
468
  end
469
469
  end
470
470
  end
@@ -22,23 +22,10 @@ module Sequel
22
22
  return scheme if scheme.is_a?(Class)
23
23
 
24
24
  scheme = scheme.to_s.gsub('-', '_').to_sym
25
-
26
- unless klass = ADAPTER_MAP[scheme]
27
- # attempt to load the adapter file
28
- begin
29
- require "sequel/adapters/#{scheme}"
30
- rescue LoadError => e
31
- raise Sequel.convert_exception_class(e, AdapterNotFound)
32
- end
33
-
34
- # make sure we actually loaded the adapter
35
- unless klass = ADAPTER_MAP[scheme]
36
- raise AdapterNotFound, "Could not load #{scheme} adapter: adapter class not registered in ADAPTER_MAP"
37
- end
38
- end
39
- klass
25
+
26
+ load_adapter(scheme)
40
27
  end
41
-
28
+
42
29
  # Returns the scheme symbol for the Database class.
43
30
  def self.adapter_scheme
44
31
  @scheme
@@ -90,6 +77,40 @@ module Sequel
90
77
  db
91
78
  end
92
79
 
80
+ # Load the adapter from the file system. Raises Sequel::AdapterNotFound
81
+ # if the adapter cannot be loaded, or if the adapter isn't registered
82
+ # correctly after being loaded. Options:
83
+ # :map :: The Hash in which to look for an already loaded adapter (defaults to ADAPTER_MAP).
84
+ # :subdir :: The subdirectory of sequel/adapters to look in, only to be used for loading
85
+ # subadapters.
86
+ def self.load_adapter(scheme, opts=OPTS)
87
+ map = opts[:map] || ADAPTER_MAP
88
+ if subdir = opts[:subdir]
89
+ file = "#{subdir}/#{scheme}"
90
+ else
91
+ file = scheme
92
+ end
93
+
94
+ unless obj = Sequel.synchronize{map[scheme]}
95
+ # attempt to load the adapter file
96
+ begin
97
+ require "sequel/adapters/#{file}"
98
+ rescue LoadError => e
99
+ # If subadapter file doesn't exist, just return,
100
+ # using the main adapter class without database customizations.
101
+ return if subdir
102
+ raise Sequel.convert_exception_class(e, AdapterNotFound)
103
+ end
104
+
105
+ # make sure we actually loaded the adapter
106
+ unless obj = Sequel.synchronize{map[scheme]}
107
+ raise AdapterNotFound, "Could not load #{file} adapter: adapter class not registered in ADAPTER_MAP"
108
+ end
109
+ end
110
+
111
+ obj
112
+ end
113
+
93
114
  # Sets the adapter scheme for the Database class. Call this method in
94
115
  # descendants of Database to allow connection using a URL. For example the
95
116
  # following:
@@ -104,7 +125,7 @@ module Sequel
104
125
  # Sequel.connect('mydb://user:password@dbserver/mydb')
105
126
  def self.set_adapter_scheme(scheme) # :nodoc:
106
127
  @scheme = scheme
107
- ADAPTER_MAP[scheme] = self
128
+ Sequel.synchronize{ADAPTER_MAP[scheme] = self}
108
129
  end
109
130
  private_class_method :set_adapter_scheme
110
131
 
@@ -1022,8 +1022,12 @@ module Sequel
1022
1022
  def unaliased_identifier(c)
1023
1023
  case c
1024
1024
  when Symbol
1025
- c_table, column, _ = split_symbol(c)
1026
- c_table ? SQL::QualifiedIdentifier.new(c_table, column.to_sym) : column.to_sym
1025
+ table, column, aliaz = split_symbol(c)
1026
+ if aliaz
1027
+ table ? SQL::QualifiedIdentifier.new(table, column) : Sequel.identifier(column)
1028
+ else
1029
+ c
1030
+ end
1027
1031
  when SQL::AliasedExpression
1028
1032
  c.expression
1029
1033
  when SQL::OrderedExpression
@@ -95,7 +95,8 @@ module Sequel
95
95
  ds = self
96
96
 
97
97
  # Use a from_self if this is already a joined table (or from_self specifically disabled for graphs)
98
- if (@opts[:graph_from_self] != false && !@opts[:graph] && (@opts[:from].length > 1 || @opts[:join]))
98
+ if (@opts[:graph_from_self] != false && !@opts[:graph] && joined_dataset?)
99
+ from_selfed = true
99
100
  implicit_qualifier = options[:from_self_alias] || first_source
100
101
  ds = ds.from_self(:alias=>implicit_qualifier)
101
102
  end
@@ -109,49 +110,46 @@ module Sequel
109
110
  # Whether to add the columns to the list of column aliases
110
111
  add_columns = !ds.opts.include?(:graph_aliases)
111
112
 
112
- # Setup the initial graph data structure if it doesn't exist
113
113
  if graph = opts[:graph]
114
114
  opts[:graph] = graph = graph.dup
115
115
  select = opts[:select].dup
116
116
  [:column_aliases, :table_aliases, :column_alias_num].each{|k| graph[k] = graph[k].dup}
117
117
  else
118
+ # Setup the initial graph data structure if it doesn't exist
118
119
  qualifier = ds.first_source_alias
119
120
  master = alias_symbol(qualifier)
120
121
  raise_alias_error.call if master == table_alias
122
+
121
123
  # Master hash storing all .graph related information
122
124
  graph = opts[:graph] = {}
125
+
123
126
  # Associates column aliases back to tables and columns
124
127
  column_aliases = graph[:column_aliases] = {}
128
+
125
129
  # Associates table alias (the master is never aliased)
126
130
  table_aliases = graph[:table_aliases] = {master=>self}
131
+
127
132
  # Keep track of the alias numbers used
128
133
  ca_num = graph[:column_alias_num] = Hash.new(0)
134
+
129
135
  # All columns in the master table are never
130
136
  # aliased, but are not included if set_graph_aliases
131
137
  # has been used.
132
138
  if add_columns
133
139
  if (select = @opts[:select]) && !select.empty? && !(select.length == 1 && (select.first.is_a?(SQL::ColumnAll)))
134
- select = select.each do |sel|
135
- column = case sel
136
- when Symbol
137
- _, c, a = split_symbol(sel)
138
- (a || c).to_sym
139
- when SQL::Identifier
140
- sel.value.to_sym
141
- when SQL::QualifiedIdentifier
142
- column = sel.column
143
- column = column.value if column.is_a?(SQL::Identifier)
144
- column.to_sym
145
- when SQL::AliasedExpression
146
- column = sel.alias
147
- column = column.value if column.is_a?(SQL::Identifier)
148
- column.to_sym
140
+ select = select.map do |sel|
141
+ raise Error, "can't figure out alias to use for graphing for #{sel.inspect}" unless column = _hash_key_symbol(sel)
142
+ column_aliases[column] = [master, column]
143
+ if from_selfed
144
+ # Initial dataset was wrapped in subselect, selected all
145
+ # columns in the subselect, qualified by the subselect alias.
146
+ Sequel.qualify(qualifier, Sequel.identifier(column))
149
147
  else
150
- raise Error, "can't figure out alias to use for graphing for #{sel.inspect}"
148
+ # Initial dataset not wrapped in subslect, just make
149
+ # sure columns are qualified in some way.
150
+ qualified_expression(sel, qualifier)
151
151
  end
152
- column_aliases[column] = [master, column]
153
152
  end
154
- select = qualified_expression(select, qualifier)
155
153
  else
156
154
  select = columns.map do |column|
157
155
  column_aliases[column] = [master, column]
@@ -169,6 +169,11 @@ module Sequel
169
169
  "#<#{visible_class_name}: #{sql.inspect}>"
170
170
  end
171
171
 
172
+ # Whether this dataset is a joined dataset (multiple FROM tables or any JOINs).
173
+ def joined_dataset?
174
+ !!((opts[:from].is_a?(Array) && opts[:from].size > 1) || opts[:join])
175
+ end
176
+
172
177
  # The alias to use for the row_number column, used when emulating OFFSET
173
178
  # support and for eager limit strategies
174
179
  def row_number_column
@@ -192,6 +197,17 @@ module Sequel
192
197
  end
193
198
  end
194
199
 
200
+ # This returns an SQL::Identifier or SQL::AliasedExpression containing an
201
+ # SQL identifier that represents the unqualified column for the given value.
202
+ # The given value should be a Symbol, SQL::Identifier, SQL::QualifiedIdentifier,
203
+ # or SQL::AliasedExpression containing one of those. In other cases, this
204
+ # returns nil
205
+ def unqualified_column_for(v)
206
+ unless v.is_a?(String)
207
+ _unqualified_column_for(v)
208
+ end
209
+ end
210
+
195
211
  # Creates a unique table alias that hasn't already been used in the dataset.
196
212
  # table_alias can be any type of object accepted by alias_symbol.
197
213
  # The symbol returned will be the implicit alias in the argument,
@@ -232,6 +248,27 @@ module Sequel
232
248
 
233
249
  private
234
250
 
251
+ # Internal recursive version of unqualified_column_for, handling Strings inside
252
+ # of other objects.
253
+ def _unqualified_column_for(v)
254
+ case v
255
+ when Symbol
256
+ _, c, a = Sequel.split_symbol(v)
257
+ c = Sequel.identifier(c)
258
+ a ? c.as(a) : c
259
+ when String
260
+ Sequel.identifier(v)
261
+ when SQL::Identifier
262
+ v
263
+ when SQL::QualifiedIdentifier
264
+ _unqualified_column_for(v.column)
265
+ when SQL::AliasedExpression
266
+ if expr = unqualified_column_for(v.expression)
267
+ SQL::AliasedExpression.new(expr, v.alias)
268
+ end
269
+ end
270
+ end
271
+
235
272
  # Return the class name for this dataset, but skip anonymous classes
236
273
  def visible_class_name
237
274
  c = self.class
@@ -86,8 +86,7 @@ module Sequel
86
86
  when :first
87
87
  clone(:limit=>1).select_sql
88
88
  when :insert_select
89
- ds = opts[:returning] ? self : returning
90
- ds.insert_sql(*@prepared_modify_values)
89
+ insert_select_sql(*@prepared_modify_values)
91
90
  when :insert
92
91
  insert_sql(*@prepared_modify_values)
93
92
  when :update
@@ -694,6 +694,7 @@ module Sequel
694
694
  # DB[:items].returning(nil) # RETURNING NULL
695
695
  # DB[:items].returning(:id, :name) # RETURNING id, name
696
696
  def returning(*values)
697
+ raise Error, "RETURNING is not supported on #{db.database_type}" unless supports_returning?(:insert)
697
698
  clone(:returning=>values)
698
699
  end
699
700
 
@@ -280,7 +280,6 @@ module Sequel
280
280
  FORMAT_DATE_STANDARD = "DATE '%Y-%m-%d'".freeze
281
281
  FORMAT_OFFSET = "%+03i%02i".freeze
282
282
  FORMAT_TIMESTAMP_RE = /%[Nz]/.freeze
283
- FORMAT_TIMESTAMP_USEC = ".%06d".freeze
284
283
  FORMAT_USEC = '%N'.freeze
285
284
  FRAME_ALL = "ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING".freeze
286
285
  FRAME_ROWS = "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW".freeze
@@ -331,11 +330,12 @@ module Sequel
331
330
  USING = ' USING ('.freeze
332
331
  UNION_ALL_SELECT = ' UNION ALL SELECT '.freeze
333
332
  VALUES = " VALUES ".freeze
334
- V190 = '1.9.0'.freeze
335
333
  WHERE = " WHERE ".freeze
336
334
  WITH_ORDINALITY = " WITH ORDINALITY".freeze
337
335
  WITHIN_GROUP = " WITHIN GROUP (ORDER BY ".freeze
338
336
 
337
+ DATETIME_SECFRACTION_ARG = RUBY_VERSION >= '1.9.0' ? 1000000 : 86400000000
338
+
339
339
  [:literal, :quote_identifier, :quote_schema_table].each do |meth|
340
340
  class_eval(<<-END, __FILE__, __LINE__ + 1)
341
341
  def #{meth}(*args, &block)
@@ -1022,7 +1022,7 @@ module Sequel
1022
1022
  v2 = db.from_application_timestamp(v)
1023
1023
  fmt = default_timestamp_format.gsub(FORMAT_TIMESTAMP_RE) do |m|
1024
1024
  if m == FORMAT_USEC
1025
- format_timestamp_usec(v.is_a?(DateTime) ? v.sec_fraction*(RUBY_VERSION < V190 ? 86400000000 : 1000000) : v.usec) if supports_timestamp_usecs?
1025
+ format_timestamp_usec(v.is_a?(DateTime) ? v.sec_fraction*(DATETIME_SECFRACTION_ARG) : v.usec) if supports_timestamp_usecs?
1026
1026
  else
1027
1027
  if supports_timestamp_timezones?
1028
1028
  # Would like to just use %z format, but it doesn't appear to work on Windows
@@ -1043,7 +1043,10 @@ module Sequel
1043
1043
  # Return the SQL timestamp fragment to use for the fractional time part.
1044
1044
  # Should start with the decimal point. Uses 6 decimal places by default.
1045
1045
  def format_timestamp_usec(usec)
1046
- sprintf(FORMAT_TIMESTAMP_USEC, usec)
1046
+ unless (ts = timestamp_precision) == 6
1047
+ usec = usec/(10 ** (6 - ts))
1048
+ end
1049
+ sprintf(".%0#{ts}d", usec)
1047
1050
  end
1048
1051
 
1049
1052
  # Append literalization of identifier to SQL string, considering regular strings
@@ -1082,7 +1085,11 @@ module Sequel
1082
1085
 
1083
1086
  def insert_into_sql(sql)
1084
1087
  sql << INTO
1085
- source_list_append(sql, @opts[:from])
1088
+ if (f = @opts[:from]) && f.length == 1
1089
+ identifier_append(sql, unaliased_identifier(f.first))
1090
+ else
1091
+ source_list_append(sql, f)
1092
+ end
1086
1093
  end
1087
1094
 
1088
1095
  def insert_columns_sql(sql)
@@ -1132,11 +1139,6 @@ module Sequel
1132
1139
  "#{join_type.to_s.gsub(UNDERSCORE, SPACE).upcase} JOIN"
1133
1140
  end
1134
1141
 
1135
- # Whether this dataset is a joined dataset
1136
- def joined_dataset?
1137
- (opts[:from].is_a?(Array) && opts[:from].size > 1) || opts[:join]
1138
- end
1139
-
1140
1142
  # Append a literalization of the array to SQL string.
1141
1143
  # Treats as an expression if an array of all two pairs, or as a SQL array otherwise.
1142
1144
  def literal_array_append(sql, v)
@@ -1513,6 +1515,11 @@ module Sequel
1513
1515
  ds.clone(:append_sql=>sql).sql
1514
1516
  end
1515
1517
 
1518
+ # The number of decimal digits of precision to use in timestamps.
1519
+ def timestamp_precision
1520
+ supports_timestamp_usecs? ? 6 : 0
1521
+ end
1522
+
1516
1523
  def update_table_sql(sql)
1517
1524
  sql << SPACE
1518
1525
  source_list_append(sql, @opts[:from])
@@ -0,0 +1,90 @@
1
+ # The dataset_source_alias extension changes Sequel's
2
+ # default behavior of automatically aliasing datasets
3
+ # from using t1, t2, etc. to using an alias based on
4
+ # the source of the dataset. Example:
5
+ #
6
+ # DB.from(DB.from(:a))
7
+ # # default: SELECT * FROM (SELECT * FROM a) AS t1
8
+ # # with extension: SELECT * FROM (SELECT * FROM a) AS a
9
+ #
10
+ # This also works when joining:
11
+ #
12
+ # DB[:a].join(DB[:b], [:id])
13
+ # # SELECT * FROM a INNER JOIN (SELECT * FROM b) AS b USING (id)
14
+ #
15
+ # To avoid conflicting aliases, this attempts to alias tables
16
+ # uniquely if it detects a conflict:
17
+ #
18
+ # DB.from(:a, DB.from(:a))
19
+ # # SELECT * FROM a, (SELECT * FROM a) AS a_0
20
+ #
21
+ # Note that not all conflicts are correctly detected and handled.
22
+ # It is encouraged to alias your datasets manually instead of
23
+ # relying on the auto-aliasing if there would be a conflict.
24
+ #
25
+ # In the places where Sequel cannot determine the
26
+ # appropriate alias to use for the dataset, it will fallback to
27
+ # the standard t1, t2, etc. aliasing.
28
+ #
29
+ # You can load this extension into specific datasets:
30
+ #
31
+ # ds = DB[:table]
32
+ # ds = ds.extension(:dataset_source_alias)
33
+ #
34
+ # Or you can load it into all of a database's datasets, which
35
+ # is probably the desired behavior if you are using this extension:
36
+ #
37
+ # DB.extension(:dataset_source_alias)
38
+
39
+ module Sequel
40
+ class Dataset
41
+ module DatasetSourceAlias
42
+ # Preprocess the list of sources and attempt to alias any
43
+ # datasets in the sources to the first source of the resepctive
44
+ # dataset.
45
+ def from(*source, &block)
46
+ virtual_row_columns(source, block)
47
+ table_aliases = []
48
+ source = source.map do |s|
49
+ case s
50
+ when Dataset
51
+ s = dataset_source_alias_expression(s, table_aliases)
52
+ when Symbol, String, SQL::AliasedExpression, SQL::Identifier, SQL::QualifiedIdentifier
53
+ table_aliases << alias_symbol(s)
54
+ end
55
+ s
56
+ end
57
+ super(*source, &nil)
58
+ end
59
+
60
+ # If a Dataset is given as the table argument, attempt to alias
61
+ # it to its source.
62
+ def join_table(type, table, expr=nil, options=OPTS)
63
+ if table.is_a?(Dataset) && !options[:table_alias]
64
+ table = dataset_source_alias_expression(table)
65
+ end
66
+ super
67
+ end
68
+
69
+ private
70
+
71
+ # Attempt to automatically alias the given dataset to its source.
72
+ # If the dataset cannot be automatically aliased to its source,
73
+ # return it unchanged. The table_aliases argument is a list of
74
+ # already used alias symbols, which will not be used as the alias.
75
+ def dataset_source_alias_expression(ds, table_aliases=[])
76
+ base = ds.first_source if ds.opts[:from]
77
+ case base
78
+ when Symbol, String, SQL::AliasedExpression, SQL::Identifier, SQL::QualifiedIdentifier
79
+ aliaz = unused_table_alias(base, table_aliases)
80
+ table_aliases << aliaz
81
+ ds.as(aliaz)
82
+ else
83
+ ds
84
+ end
85
+ end
86
+ end
87
+
88
+ register_extension(:dataset_source_alias, DatasetSourceAlias)
89
+ end
90
+ end