sequel 4.12.0 → 4.13.0

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