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.
- 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
|