sequel 4.12.0 → 4.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +64 -0
- data/Rakefile +3 -1
- data/bin/sequel +13 -5
- data/doc/release_notes/4.13.0.txt +169 -0
- data/doc/sql.rdoc +3 -3
- data/lib/sequel/adapters/do.rb +11 -23
- data/lib/sequel/adapters/do/mysql.rb +8 -0
- data/lib/sequel/adapters/do/postgres.rb +8 -0
- data/lib/sequel/adapters/do/{sqlite.rb → sqlite3.rb} +9 -0
- data/lib/sequel/adapters/jdbc.rb +16 -139
- data/lib/sequel/adapters/jdbc/as400.rb +9 -0
- data/lib/sequel/adapters/jdbc/cubrid.rb +9 -0
- data/lib/sequel/adapters/jdbc/db2.rb +9 -0
- data/lib/sequel/adapters/jdbc/derby.rb +9 -0
- data/lib/sequel/adapters/jdbc/{firebird.rb → firebirdsql.rb} +9 -0
- data/lib/sequel/adapters/jdbc/h2.rb +10 -0
- data/lib/sequel/adapters/jdbc/hsqldb.rb +9 -0
- data/lib/sequel/adapters/jdbc/{informix.rb → informix-sqli.rb} +9 -0
- data/lib/sequel/adapters/jdbc/{progress.rb → jdbcprogress.rb} +9 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +10 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +14 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +9 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +9 -0
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +23 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +10 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +10 -0
- data/lib/sequel/adapters/odbc.rb +6 -14
- data/lib/sequel/adapters/odbc/db2.rb +9 -0
- data/lib/sequel/adapters/odbc/mssql.rb +8 -0
- data/lib/sequel/adapters/odbc/progress.rb +8 -0
- data/lib/sequel/adapters/oracle.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +1 -1
- data/lib/sequel/adapters/shared/firebird.rb +8 -1
- data/lib/sequel/adapters/shared/mssql.rb +68 -27
- data/lib/sequel/adapters/shared/mysql.rb +3 -5
- data/lib/sequel/adapters/shared/oracle.rb +17 -3
- data/lib/sequel/adapters/shared/postgres.rb +9 -4
- data/lib/sequel/adapters/shared/sqlanywhere.rb +6 -6
- data/lib/sequel/database/connecting.rb +38 -17
- data/lib/sequel/dataset/actions.rb +6 -2
- data/lib/sequel/dataset/graph.rb +18 -20
- data/lib/sequel/dataset/misc.rb +37 -0
- data/lib/sequel/dataset/prepared_statements.rb +1 -2
- data/lib/sequel/dataset/query.rb +1 -0
- data/lib/sequel/dataset/sql.rb +17 -10
- data/lib/sequel/extensions/dataset_source_alias.rb +90 -0
- data/lib/sequel/extensions/pg_array.rb +14 -10
- data/lib/sequel/extensions/pg_enum.rb +135 -0
- data/lib/sequel/extensions/pg_hstore.rb +4 -6
- data/lib/sequel/extensions/pg_inet.rb +4 -5
- data/lib/sequel/extensions/pg_interval.rb +3 -3
- data/lib/sequel/extensions/pg_json.rb +16 -12
- data/lib/sequel/extensions/pg_range.rb +5 -3
- data/lib/sequel/extensions/pg_row.rb +2 -2
- data/lib/sequel/extensions/round_timestamps.rb +52 -0
- data/lib/sequel/model.rb +5 -2
- data/lib/sequel/model/associations.rb +29 -3
- data/lib/sequel/model/base.rb +68 -29
- data/lib/sequel/plugins/class_table_inheritance.rb +25 -16
- data/lib/sequel/plugins/column_select.rb +57 -0
- data/lib/sequel/plugins/composition.rb +14 -16
- data/lib/sequel/plugins/dirty.rb +9 -11
- data/lib/sequel/plugins/insert_returning_select.rb +70 -0
- data/lib/sequel/plugins/instance_filters.rb +7 -9
- data/lib/sequel/plugins/lazy_attributes.rb +16 -4
- data/lib/sequel/plugins/list.rb +9 -0
- data/lib/sequel/plugins/modification_detection.rb +90 -0
- data/lib/sequel/plugins/serialization.rb +13 -15
- data/lib/sequel/plugins/serialization_modification_detection.rb +9 -9
- data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
- data/lib/sequel/plugins/timestamps.rb +6 -6
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mysql_spec.rb +7 -0
- data/spec/adapters/postgres_spec.rb +41 -0
- data/spec/bin_spec.rb +4 -1
- data/spec/core/database_spec.rb +6 -0
- data/spec/core/dataset_spec.rb +100 -90
- data/spec/core/object_graph_spec.rb +5 -0
- data/spec/extensions/class_table_inheritance_spec.rb +18 -13
- data/spec/extensions/column_select_spec.rb +108 -0
- data/spec/extensions/composition_spec.rb +20 -0
- data/spec/extensions/dataset_source_alias_spec.rb +51 -0
- data/spec/extensions/insert_returning_select_spec.rb +46 -0
- data/spec/extensions/lazy_attributes_spec.rb +24 -20
- data/spec/extensions/list_spec.rb +5 -0
- data/spec/extensions/modification_detection_spec.rb +80 -0
- data/spec/extensions/pg_enum_spec.rb +64 -0
- data/spec/extensions/pg_json_spec.rb +7 -13
- data/spec/extensions/prepared_statements_spec.rb +6 -4
- data/spec/extensions/round_timestamps_spec.rb +43 -0
- data/spec/extensions/serialization_modification_detection_spec.rb +10 -1
- data/spec/extensions/serialization_spec.rb +18 -0
- data/spec/extensions/single_table_inheritance_spec.rb +5 -0
- data/spec/extensions/timestamps_spec.rb +6 -0
- data/spec/integration/plugin_test.rb +14 -8
- data/spec/integration/prepared_statement_test.rb +12 -0
- data/spec/model/associations_spec.rb +24 -0
- data/spec/model/model_spec.rb +13 -3
- data/spec/model/record_spec.rb +24 -1
- 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
|
-
|
371
|
+
key = c[:key] || key_proc.call(t)
|
372
372
|
|
373
|
-
|
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(:
|
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(:
|
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(:
|
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
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
1026
|
-
|
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
|
data/lib/sequel/dataset/graph.rb
CHANGED
@@ -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] &&
|
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.
|
135
|
-
column =
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
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]
|
data/lib/sequel/dataset/misc.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -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
|
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -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*(
|
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
|
-
|
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
|
-
|
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
|