sequel 3.21.0 → 3.29.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.
- data/CHANGELOG +413 -3
- data/README.rdoc +20 -6
- data/Rakefile +23 -16
- data/bin/sequel +1 -5
- data/doc/association_basics.rdoc +210 -43
- data/doc/dataset_basics.rdoc +4 -4
- data/doc/mass_assignment.rdoc +54 -0
- data/doc/migration.rdoc +15 -538
- data/doc/model_hooks.rdoc +72 -27
- data/doc/opening_databases.rdoc +86 -50
- data/doc/prepared_statements.rdoc +40 -13
- data/doc/reflection.rdoc +8 -2
- data/doc/release_notes/3.22.0.txt +39 -0
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/release_notes/3.24.0.txt +420 -0
- data/doc/release_notes/3.25.0.txt +88 -0
- data/doc/release_notes/3.26.0.txt +88 -0
- data/doc/release_notes/3.27.0.txt +82 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/release_notes/3.29.0.txt +459 -0
- data/doc/schema_modification.rdoc +547 -0
- data/doc/sharding.rdoc +7 -1
- data/doc/testing.rdoc +115 -0
- data/doc/transactions.rdoc +137 -0
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado/mssql.rb +18 -6
- data/lib/sequel/adapters/ado.rb +40 -18
- data/lib/sequel/adapters/amalgalite.rb +15 -7
- data/lib/sequel/adapters/db2.rb +175 -86
- data/lib/sequel/adapters/dbi.rb +15 -15
- data/lib/sequel/adapters/do/mysql.rb +0 -5
- data/lib/sequel/adapters/do/postgres.rb +0 -5
- data/lib/sequel/adapters/do/sqlite.rb +0 -5
- data/lib/sequel/adapters/do.rb +17 -36
- data/lib/sequel/adapters/firebird.rb +27 -208
- data/lib/sequel/adapters/ibmdb.rb +448 -0
- data/lib/sequel/adapters/informix.rb +6 -23
- data/lib/sequel/adapters/jdbc/as400.rb +5 -31
- data/lib/sequel/adapters/jdbc/db2.rb +44 -0
- data/lib/sequel/adapters/jdbc/derby.rb +217 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
- data/lib/sequel/adapters/jdbc/h2.rb +26 -17
- data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
- data/lib/sequel/adapters/jdbc/informix.rb +26 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -10
- data/lib/sequel/adapters/jdbc/oracle.rb +67 -25
- data/lib/sequel/adapters/jdbc/postgresql.rb +6 -11
- data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
- data/lib/sequel/adapters/jdbc/sqlserver.rb +41 -0
- data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
- data/lib/sequel/adapters/jdbc.rb +107 -38
- data/lib/sequel/adapters/mock.rb +315 -0
- data/lib/sequel/adapters/mysql.rb +78 -182
- data/lib/sequel/adapters/mysql2.rb +24 -23
- data/lib/sequel/adapters/odbc/db2.rb +17 -0
- data/lib/sequel/adapters/odbc/mssql.rb +0 -5
- data/lib/sequel/adapters/odbc.rb +28 -9
- data/lib/sequel/adapters/openbase.rb +2 -4
- data/lib/sequel/adapters/oracle.rb +349 -51
- data/lib/sequel/adapters/postgres.rb +169 -21
- data/lib/sequel/adapters/shared/access.rb +21 -6
- data/lib/sequel/adapters/shared/db2.rb +288 -0
- data/lib/sequel/adapters/shared/firebird.rb +214 -0
- data/lib/sequel/adapters/shared/informix.rb +45 -0
- data/lib/sequel/adapters/shared/mssql.rb +108 -65
- data/lib/sequel/adapters/shared/mysql.rb +56 -13
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
- data/lib/sequel/adapters/shared/oracle.rb +185 -34
- data/lib/sequel/adapters/shared/postgres.rb +107 -58
- data/lib/sequel/adapters/shared/progress.rb +0 -6
- data/lib/sequel/adapters/shared/sqlite.rb +168 -41
- data/lib/sequel/adapters/sqlite.rb +27 -18
- data/lib/sequel/adapters/swift/mysql.rb +9 -5
- data/lib/sequel/adapters/swift/postgres.rb +0 -5
- data/lib/sequel/adapters/swift/sqlite.rb +6 -4
- data/lib/sequel/adapters/swift.rb +5 -5
- data/lib/sequel/adapters/tinytds.rb +152 -17
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +63 -0
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/connection_pool/threaded.rb +3 -2
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core.rb +46 -5
- data/lib/sequel/database/connecting.rb +5 -6
- data/lib/sequel/database/dataset.rb +4 -4
- data/lib/sequel/database/dataset_defaults.rb +59 -1
- data/lib/sequel/database/logging.rb +1 -1
- data/lib/sequel/database/misc.rb +100 -19
- data/lib/sequel/database/query.rb +153 -58
- data/lib/sequel/database/schema_generator.rb +8 -5
- data/lib/sequel/database/schema_methods.rb +59 -29
- data/lib/sequel/dataset/actions.rb +194 -57
- data/lib/sequel/dataset/features.rb +81 -8
- data/lib/sequel/dataset/graph.rb +8 -7
- data/lib/sequel/dataset/misc.rb +50 -23
- data/lib/sequel/dataset/mutation.rb +5 -6
- data/lib/sequel/dataset/prepared_statements.rb +32 -15
- data/lib/sequel/dataset/query.rb +223 -42
- data/lib/sequel/dataset/sql.rb +58 -61
- data/lib/sequel/dataset.rb +8 -0
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/columns_introspection.rb +61 -0
- data/lib/sequel/extensions/migration.rb +6 -4
- data/lib/sequel/extensions/named_timezones.rb +5 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model/associations.rb +997 -331
- data/lib/sequel/model/base.rb +386 -129
- data/lib/sequel/model/errors.rb +1 -1
- data/lib/sequel/model/exceptions.rb +5 -1
- data/lib/sequel/model.rb +15 -8
- data/lib/sequel/plugins/association_pks.rb +22 -4
- data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
- data/lib/sequel/plugins/dataset_associations.rb +100 -0
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/force_encoding.rb +6 -6
- data/lib/sequel/plugins/identity_map.rb +114 -7
- data/lib/sequel/plugins/many_through_many.rb +65 -7
- data/lib/sequel/plugins/prepared_statements.rb +151 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/plugins/rcte_tree.rb +29 -15
- data/lib/sequel/plugins/serialization.rb +6 -1
- data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
- data/lib/sequel/plugins/sharding.rb +12 -25
- data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
- data/lib/sequel/plugins/typecast_on_load.rb +9 -12
- data/lib/sequel/plugins/update_primary_key.rb +2 -2
- data/lib/sequel/plugins/xml_serializer.rb +3 -3
- data/lib/sequel/sql.rb +107 -51
- data/lib/sequel/timezones.rb +44 -35
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/mssql_spec.rb +65 -29
- data/spec/adapters/mysql_spec.rb +122 -123
- data/spec/adapters/oracle_spec.rb +48 -76
- data/spec/adapters/postgres_spec.rb +213 -61
- data/spec/adapters/spec_helper.rb +6 -5
- data/spec/adapters/sqlite_spec.rb +35 -21
- data/spec/core/connection_pool_spec.rb +71 -92
- data/spec/core/core_sql_spec.rb +20 -31
- data/spec/core/database_spec.rb +729 -508
- data/spec/core/dataset_spec.rb +980 -1044
- data/spec/core/expression_filters_spec.rb +159 -42
- data/spec/core/mock_adapter_spec.rb +378 -0
- data/spec/core/object_graph_spec.rb +48 -114
- data/spec/core/schema_generator_spec.rb +3 -3
- data/spec/core/schema_spec.rb +298 -38
- data/spec/core/spec_helper.rb +6 -48
- data/spec/extensions/association_pks_spec.rb +38 -0
- data/spec/extensions/class_table_inheritance_spec.rb +1 -1
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/dataset_associations_spec.rb +199 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/identity_map_spec.rb +162 -0
- data/spec/extensions/instance_hooks_spec.rb +71 -0
- data/spec/extensions/many_through_many_spec.rb +195 -20
- data/spec/extensions/migration_spec.rb +17 -17
- data/spec/extensions/named_timezones_spec.rb +22 -2
- data/spec/extensions/nested_attributes_spec.rb +4 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
- data/spec/extensions/prepared_statements_spec.rb +72 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
- data/spec/extensions/schema_dumper_spec.rb +2 -2
- data/spec/extensions/schema_spec.rb +13 -21
- data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
- data/spec/extensions/serialization_spec.rb +5 -8
- data/spec/extensions/single_table_inheritance_spec.rb +11 -0
- data/spec/extensions/spec_helper.rb +7 -1
- data/spec/extensions/thread_local_timezones_spec.rb +22 -2
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/extensions/typecast_on_load_spec.rb +1 -6
- data/spec/extensions/xml_serializer_spec.rb +16 -4
- data/spec/integration/associations_test.rb +522 -21
- data/spec/integration/database_test.rb +4 -2
- data/spec/integration/dataset_test.rb +375 -62
- data/spec/integration/eager_loader_test.rb +19 -21
- data/spec/integration/model_test.rb +80 -1
- data/spec/integration/plugin_test.rb +304 -116
- data/spec/integration/prepared_statement_test.rb +200 -120
- data/spec/integration/schema_test.rb +161 -30
- data/spec/integration/spec_helper.rb +39 -30
- data/spec/integration/timezone_test.rb +38 -12
- data/spec/integration/transaction_test.rb +172 -5
- data/spec/integration/type_test.rb +17 -3
- data/spec/model/association_reflection_spec.rb +174 -7
- data/spec/model/associations_spec.rb +849 -661
- data/spec/model/base_spec.rb +255 -95
- data/spec/model/dataset_methods_spec.rb +7 -27
- data/spec/model/eager_loading_spec.rb +640 -676
- data/spec/model/hooks_spec.rb +309 -67
- data/spec/model/model_spec.rb +207 -167
- data/spec/model/plugins_spec.rb +24 -13
- data/spec/model/record_spec.rb +321 -218
- data/spec/model/spec_helper.rb +13 -71
- data/spec/model/validations_spec.rb +11 -0
- metadata +95 -38
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Firebird
|
|
3
|
+
module DatabaseMethods
|
|
4
|
+
AUTO_INCREMENT = ''.freeze
|
|
5
|
+
TEMPORARY = 'GLOBAL TEMPORARY '.freeze
|
|
6
|
+
|
|
7
|
+
def clear_primary_key(*tables)
|
|
8
|
+
tables.each{|t| @primary_keys.delete(dataset.send(:input_identifier, t))}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def create_trigger(*args)
|
|
12
|
+
self << create_trigger_sql(*args)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def database_type
|
|
16
|
+
:firebird
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def drop_sequence(name)
|
|
20
|
+
self << drop_sequence_sql(name)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Return primary key for the given table.
|
|
24
|
+
def primary_key(table)
|
|
25
|
+
t = dataset.send(:input_identifier, table)
|
|
26
|
+
@primary_keys.fetch(t) do
|
|
27
|
+
pk = fetch("SELECT RDB$FIELD_NAME FROM RDB$INDEX_SEGMENTS NATURAL JOIN RDB$RELATION_CONSTRAINTS WHERE RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' AND RDB$RELATION_NAME = ?", t).single_value
|
|
28
|
+
@primary_keys[t] = dataset.send(:output_identifier, pk.rstrip) if pk
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def restart_sequence(*args)
|
|
33
|
+
self << restart_sequence_sql(*args)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def sequences(opts={})
|
|
37
|
+
ds = self[:"rdb$generators"].server(opts[:server]).filter(:"rdb$system_flag" => 0).select(:"rdb$generator_name")
|
|
38
|
+
block_given? ? yield(ds) : ds.map{|r| ds.send(:output_identifier, r[:"rdb$generator_name"])}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def tables(opts={})
|
|
42
|
+
tables_or_views(0, opts)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def views(opts={})
|
|
46
|
+
tables_or_views(1, opts)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# Use Firebird specific syntax for add column
|
|
52
|
+
def alter_table_sql(table, op)
|
|
53
|
+
case op[:op]
|
|
54
|
+
when :add_column
|
|
55
|
+
"ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
|
|
56
|
+
when :drop_column
|
|
57
|
+
"ALTER TABLE #{quote_schema_table(table)} DROP #{column_definition_sql(op)}"
|
|
58
|
+
when :rename_column
|
|
59
|
+
"ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} TO #{quote_identifier(op[:new_name])}"
|
|
60
|
+
when :set_column_type
|
|
61
|
+
"ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} TYPE #{type_literal(op)}"
|
|
62
|
+
else
|
|
63
|
+
super(table, op)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def auto_increment_sql()
|
|
68
|
+
AUTO_INCREMENT
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def create_sequence_sql(name, opts={})
|
|
72
|
+
"CREATE SEQUENCE #{quote_identifier(name)}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Firebird gets an override because of the mess of creating a
|
|
76
|
+
# sequence and trigger for auto-incrementing primary keys.
|
|
77
|
+
def create_table_from_generator(name, generator, options)
|
|
78
|
+
drop_statement, create_statements = create_table_sql_list(name, generator, options)
|
|
79
|
+
(execute_ddl(drop_statement) rescue nil) if drop_statement
|
|
80
|
+
create_statements.each{|sql| execute_ddl(sql)}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def create_table_sql_list(name, generator, options={})
|
|
84
|
+
statements = [create_table_sql(name, generator, options)]
|
|
85
|
+
drop_seq_statement = nil
|
|
86
|
+
generator.columns.each do |c|
|
|
87
|
+
if c[:auto_increment]
|
|
88
|
+
c[:sequence_name] ||= "seq_#{name}_#{c[:name]}"
|
|
89
|
+
unless c[:create_sequence] == false
|
|
90
|
+
drop_seq_statement = drop_sequence_sql(c[:sequence_name])
|
|
91
|
+
statements << create_sequence_sql(c[:sequence_name])
|
|
92
|
+
statements << restart_sequence_sql(c[:sequence_name], {:restart_position => c[:sequence_start_position]}) if c[:sequence_start_position]
|
|
93
|
+
end
|
|
94
|
+
unless c[:create_trigger] == false
|
|
95
|
+
c[:trigger_name] ||= "BI_#{name}_#{c[:name]}"
|
|
96
|
+
c[:quoted_name] = quote_identifier(c[:name])
|
|
97
|
+
trigger_definition = <<-END
|
|
98
|
+
begin
|
|
99
|
+
if ((new.#{c[:quoted_name]} is null) or (new.#{c[:quoted_name]} = 0)) then
|
|
100
|
+
begin
|
|
101
|
+
new.#{c[:quoted_name]} = next value for #{c[:sequence_name]};
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
END
|
|
105
|
+
statements << create_trigger_sql(name, c[:trigger_name], trigger_definition, {:events => [:insert]})
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
[drop_seq_statement, statements]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def create_trigger_sql(table, name, definition, opts={})
|
|
113
|
+
events = opts[:events] ? Array(opts[:events]) : [:insert, :update, :delete]
|
|
114
|
+
whence = opts[:after] ? 'AFTER' : 'BEFORE'
|
|
115
|
+
inactive = opts[:inactive] ? 'INACTIVE' : 'ACTIVE'
|
|
116
|
+
position = opts.fetch(:position, 0)
|
|
117
|
+
sql = <<-end_sql
|
|
118
|
+
CREATE TRIGGER #{quote_identifier(name)} for #{quote_identifier(table)}
|
|
119
|
+
#{inactive} #{whence} #{events.map{|e| e.to_s.upcase}.join(' OR ')} position #{position}
|
|
120
|
+
as #{definition}
|
|
121
|
+
end_sql
|
|
122
|
+
sql
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def drop_sequence_sql(name)
|
|
126
|
+
"DROP SEQUENCE #{quote_identifier(name)}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def remove_cached_schema(table)
|
|
130
|
+
clear_primary_key(table)
|
|
131
|
+
super
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def restart_sequence_sql(name, opts={})
|
|
135
|
+
seq_name = quote_identifier(name)
|
|
136
|
+
"ALTER SEQUENCE #{seq_name} RESTART WITH #{opts[:restart_position]}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def tables_or_views(type, opts)
|
|
140
|
+
ds = self[:"rdb$relations"].server(opts[:server]).filter(:"rdb$relation_type" => type, Sequel::SQL::Function.new(:COALESCE, :"rdb$system_flag", 0) => 0).select(:"rdb$relation_name")
|
|
141
|
+
ds.map{|r| ds.send(:output_identifier, r[:"rdb$relation_name"].rstrip)}
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def type_literal_generic_string(column)
|
|
145
|
+
column[:text] ? :"BLOB SUB_TYPE TEXT" : super
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
module DatasetMethods
|
|
150
|
+
BOOL_TRUE = '1'.freeze
|
|
151
|
+
BOOL_FALSE = '0'.freeze
|
|
152
|
+
NULL = LiteralString.new('NULL').freeze
|
|
153
|
+
COMMA_SEPARATOR = ', '.freeze
|
|
154
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct limit columns from join where group having compounds order')
|
|
155
|
+
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'into columns values returning')
|
|
156
|
+
|
|
157
|
+
# Insert given values into the database.
|
|
158
|
+
def insert(*values)
|
|
159
|
+
if @opts[:sql] || @opts[:returning]
|
|
160
|
+
super
|
|
161
|
+
else
|
|
162
|
+
returning(insert_pk).insert(*values){|r| return r.values.first}
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Insert a record returning the record inserted
|
|
167
|
+
def insert_select(*values)
|
|
168
|
+
returning.insert(*values){|r| return r}
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def requires_sql_standard_datetimes?
|
|
172
|
+
true
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def supports_insert_select?
|
|
176
|
+
true
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Firebird does not support INTERSECT or EXCEPT
|
|
180
|
+
def supports_intersect_except?
|
|
181
|
+
false
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
def insert_clause_methods
|
|
187
|
+
INSERT_CLAUSE_METHODS
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def insert_pk(*values)
|
|
191
|
+
pk = db.primary_key(opts[:from].first)
|
|
192
|
+
pk ? Sequel::SQL::Identifier.new(pk) : NULL
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def literal_false
|
|
196
|
+
BOOL_FALSE
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def literal_true
|
|
200
|
+
BOOL_TRUE
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# The order of clauses in the SELECT SQL statement
|
|
204
|
+
def select_clause_methods
|
|
205
|
+
SELECT_CLAUSE_METHODS
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def select_limit_sql(sql)
|
|
209
|
+
sql << " FIRST #{@opts[:limit]}" if @opts[:limit]
|
|
210
|
+
sql << " SKIP #{@opts[:offset]}" if @opts[:offset]
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Informix
|
|
3
|
+
module DatabaseMethods
|
|
4
|
+
TEMPORARY = 'TEMP '.freeze
|
|
5
|
+
|
|
6
|
+
# Informix uses the :informix database type
|
|
7
|
+
def database_type
|
|
8
|
+
:informix
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Informix has issues with quoted identifiers, so
|
|
14
|
+
# turn off database quoting by default.
|
|
15
|
+
def quote_identifiers_default
|
|
16
|
+
false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# SQL fragment for showing a table is temporary
|
|
20
|
+
def temporary_table_sql
|
|
21
|
+
TEMPORARY
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module DatasetMethods
|
|
26
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'limit distinct columns from join where having group compounds order')
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# Informix does not support INTERSECT or EXCEPT
|
|
31
|
+
def supports_intersect_except?
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def select_clause_methods
|
|
36
|
+
SELECT_CLAUSE_METHODS
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def select_limit_sql(sql)
|
|
40
|
+
sql << " SKIP #{@opts[:offset]}" if @opts[:offset]
|
|
41
|
+
sql << " FIRST #{@opts[:limit]}" if @opts[:limit]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
Sequel.require 'adapters/utils/emulate_offset_with_row_number'
|
|
2
|
+
|
|
1
3
|
module Sequel
|
|
2
4
|
Dataset::NON_SQL_OPTIONS << :disable_insert_output
|
|
3
5
|
module MSSQL
|
|
@@ -11,10 +13,16 @@ module Sequel
|
|
|
11
13
|
SQL_ROLLBACK_TO_SAVEPOINT = 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION autopoint_%d'.freeze
|
|
12
14
|
SQL_SAVEPOINT = 'SAVE TRANSACTION autopoint_%d'.freeze
|
|
13
15
|
|
|
16
|
+
# Whether to use N'' to quote strings, which allows unicode characters inside the
|
|
17
|
+
# strings. True by default for compatibility, can be set to false for a possible
|
|
18
|
+
# performance increase. This sets the default for all datasets created from this
|
|
19
|
+
# Database object.
|
|
20
|
+
attr_accessor :mssql_unicode_strings
|
|
21
|
+
|
|
14
22
|
# The types to check for 0 scale to transform :decimal types
|
|
15
23
|
# to :integer.
|
|
16
24
|
DECIMAL_TYPE_RE = /number|numeric|decimal/io
|
|
17
|
-
|
|
25
|
+
|
|
18
26
|
# Microsoft SQL Server uses the :mssql type.
|
|
19
27
|
def database_type
|
|
20
28
|
:mssql
|
|
@@ -47,11 +55,13 @@ module Sequel
|
|
|
47
55
|
# Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
|
|
48
56
|
# information on tables.
|
|
49
57
|
def tables(opts={})
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
information_schema_tables('BASE TABLE', opts)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
|
|
62
|
+
# information on views.
|
|
63
|
+
def views(opts={})
|
|
64
|
+
information_schema_tables('VIEW', opts)
|
|
55
65
|
end
|
|
56
66
|
|
|
57
67
|
private
|
|
@@ -97,7 +107,7 @@ module Sequel
|
|
|
97
107
|
# Commit the active transaction on the connection, does not commit/release
|
|
98
108
|
# savepoints.
|
|
99
109
|
def commit_transaction(conn, opts={})
|
|
100
|
-
log_connection_execute(conn, commit_transaction_sql) unless
|
|
110
|
+
log_connection_execute(conn, commit_transaction_sql) unless @transactions[conn][:savepoint_level] > 1
|
|
101
111
|
end
|
|
102
112
|
|
|
103
113
|
# SQL to COMMIT a transaction.
|
|
@@ -117,6 +127,15 @@ module Sequel
|
|
|
117
127
|
"DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))} ON #{quote_schema_table(table)}"
|
|
118
128
|
end
|
|
119
129
|
|
|
130
|
+
# Backbone of the tables and views support.
|
|
131
|
+
def information_schema_tables(type, opts)
|
|
132
|
+
m = output_identifier_meth
|
|
133
|
+
metadata_dataset.from(:information_schema__tables___t).
|
|
134
|
+
select(:table_name).
|
|
135
|
+
filter(:table_type=>type, :table_schema=>(opts[:schema]||default_schema||'dbo').to_s).
|
|
136
|
+
map{|x| m.call(x[:table_name])}
|
|
137
|
+
end
|
|
138
|
+
|
|
120
139
|
# Always quote identifiers in the metadata_dataset, so schema parsing works.
|
|
121
140
|
def metadata_dataset
|
|
122
141
|
ds = super
|
|
@@ -139,20 +158,41 @@ module Sequel
|
|
|
139
158
|
SQL_ROLLBACK
|
|
140
159
|
end
|
|
141
160
|
|
|
142
|
-
# MSSQL
|
|
143
|
-
|
|
161
|
+
# The closest MSSQL equivalent of a boolean datatype is the bit type.
|
|
162
|
+
def schema_column_type(db_type)
|
|
163
|
+
case db_type
|
|
164
|
+
when /\A(bit)\z/io
|
|
165
|
+
:boolean
|
|
166
|
+
else
|
|
167
|
+
super
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# MSSQL uses the INFORMATION_SCHEMA to hold column information, and
|
|
172
|
+
# parses primary key information from the sysindexes, sysindexkeys,
|
|
173
|
+
# and syscolumns system tables.
|
|
144
174
|
def schema_parse_table(table_name, opts)
|
|
145
|
-
m = output_identifier_meth
|
|
146
|
-
m2 = input_identifier_meth
|
|
175
|
+
m = output_identifier_meth(opts[:dataset])
|
|
176
|
+
m2 = input_identifier_meth(opts[:dataset])
|
|
177
|
+
tn = m2.call(table_name.to_s)
|
|
178
|
+
table_id = get{object_id(tn)}
|
|
179
|
+
pk_index_id = metadata_dataset.from(:sysindexes).
|
|
180
|
+
where(:id=>table_id, :indid=>1..254){{(status & 2048)=>2048}}.
|
|
181
|
+
get(:indid)
|
|
182
|
+
pk_cols = metadata_dataset.from(:sysindexkeys___sik).
|
|
183
|
+
join(:syscolumns___sc, :id=>:id, :colid=>:colid).
|
|
184
|
+
where(:sik__id=>table_id, :sik__indid=>pk_index_id).
|
|
185
|
+
select_order_map(:sc__name)
|
|
147
186
|
ds = metadata_dataset.from(:information_schema__tables___t).
|
|
148
187
|
join(:information_schema__columns___c, :table_catalog=>:table_catalog,
|
|
149
188
|
:table_schema => :table_schema, :table_name => :table_name).
|
|
150
189
|
select(:column_name___column, :data_type___db_type, :character_maximum_length___max_chars, :column_default___default, :is_nullable___allow_null, :numeric_precision___column_size, :numeric_scale___scale).
|
|
151
|
-
filter(:c__table_name=>
|
|
190
|
+
filter(:c__table_name=>tn)
|
|
152
191
|
if schema = opts[:schema] || default_schema
|
|
153
192
|
ds.filter!(:c__table_schema=>schema)
|
|
154
193
|
end
|
|
155
194
|
ds.map do |row|
|
|
195
|
+
row[:primary_key] = pk_cols.include?(row[:column])
|
|
156
196
|
row[:allow_null] = row[:allow_null] == 'YES' ? true : false
|
|
157
197
|
row[:default] = nil if blank_object?(row[:default])
|
|
158
198
|
row[:type] = if row[:db_type] =~ DECIMAL_TYPE_RE && row[:scale] == 0
|
|
@@ -163,6 +203,11 @@ module Sequel
|
|
|
163
203
|
[m.call(row.delete(:column)), row]
|
|
164
204
|
end
|
|
165
205
|
end
|
|
206
|
+
|
|
207
|
+
# Set the mssql_unicode_strings settings from the given options.
|
|
208
|
+
def set_mssql_unicode_strings
|
|
209
|
+
@mssql_unicode_strings = typecast_value_boolean(@opts.fetch(:mssql_unicode_strings, true))
|
|
210
|
+
end
|
|
166
211
|
|
|
167
212
|
# MSSQL has both datetime and timestamp classes, most people are going
|
|
168
213
|
# to want datetime
|
|
@@ -199,6 +244,8 @@ module Sequel
|
|
|
199
244
|
end
|
|
200
245
|
|
|
201
246
|
module DatasetMethods
|
|
247
|
+
include EmulateOffsetWithRowNumber
|
|
248
|
+
|
|
202
249
|
BOOL_TRUE = '1'.freeze
|
|
203
250
|
BOOL_FALSE = '0'.freeze
|
|
204
251
|
COMMA_SEPARATOR = ', '.freeze
|
|
@@ -210,20 +257,37 @@ module Sequel
|
|
|
210
257
|
UPDLOCK = ' WITH (UPDLOCK)'.freeze
|
|
211
258
|
WILDCARD = LiteralString.new('*').freeze
|
|
212
259
|
CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
|
|
260
|
+
EXTRACT_MAP = {:year=>"yy", :month=>"m", :day=>"d", :hour=>"hh", :minute=>"n", :second=>"s"}
|
|
261
|
+
|
|
262
|
+
# Allow overriding of the mssql_unicode_strings option at the dataset level.
|
|
263
|
+
attr_accessor :mssql_unicode_strings
|
|
264
|
+
|
|
265
|
+
# Copy the mssql_unicode_strings option from the +db+ object.
|
|
266
|
+
def initialize(db, opts={})
|
|
267
|
+
super
|
|
268
|
+
@mssql_unicode_strings = db.mssql_unicode_strings
|
|
269
|
+
end
|
|
213
270
|
|
|
214
271
|
# MSSQL uses + for string concatenation, and LIKE is case insensitive by default.
|
|
215
272
|
def complex_expression_sql(op, args)
|
|
216
273
|
case op
|
|
217
274
|
when :'||'
|
|
218
275
|
super(:+, args)
|
|
219
|
-
when :
|
|
220
|
-
super(
|
|
221
|
-
when :"NOT ILIKE"
|
|
222
|
-
super(:"NOT LIKE", args)
|
|
276
|
+
when :LIKE, :"NOT LIKE"
|
|
277
|
+
super(op, args.map{|a| LiteralString.new("(#{literal(a)} COLLATE Latin1_General_CS_AS)")})
|
|
278
|
+
when :ILIKE, :"NOT ILIKE"
|
|
279
|
+
super((op == :ILIKE ? :LIKE : :"NOT LIKE"), args)
|
|
223
280
|
when :<<
|
|
224
|
-
"(#{literal(
|
|
281
|
+
complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
|
|
225
282
|
when :>>
|
|
226
|
-
"(#{literal(
|
|
283
|
+
complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
|
|
284
|
+
when :extract
|
|
285
|
+
part = args.at(0)
|
|
286
|
+
raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
|
|
287
|
+
expr = literal(args.at(1))
|
|
288
|
+
s = "datepart(#{format}, #{expr})"
|
|
289
|
+
s = "CAST((#{s} + datepart(ns, #{expr})/1000000000.0) AS double precision)" if part == :second
|
|
290
|
+
s
|
|
227
291
|
else
|
|
228
292
|
super(op, args)
|
|
229
293
|
end
|
|
@@ -244,12 +308,6 @@ module Sequel
|
|
|
244
308
|
mutation_method(:disable_insert_output)
|
|
245
309
|
end
|
|
246
310
|
|
|
247
|
-
# When returning all rows, if an offset is used, delete the row_number column
|
|
248
|
-
# before yielding the row.
|
|
249
|
-
def fetch_rows(sql, &block)
|
|
250
|
-
@opts[:offset] ? super(sql){|r| r.delete(row_number_column); yield r} : super(sql, &block)
|
|
251
|
-
end
|
|
252
|
-
|
|
253
311
|
# MSSQL uses the CONTAINS keyword for full text search
|
|
254
312
|
def full_text_search(cols, terms, opts = {})
|
|
255
313
|
filter("CONTAINS (#{literal(cols)}, #{literal(terms)})")
|
|
@@ -257,8 +315,8 @@ module Sequel
|
|
|
257
315
|
|
|
258
316
|
# Use the OUTPUT clause to get the value of all columns for the newly inserted record.
|
|
259
317
|
def insert_select(*values)
|
|
260
|
-
return unless
|
|
261
|
-
naked.clone(default_server_opts(:sql=>output(nil, [:inserted
|
|
318
|
+
return unless supports_insert_select?
|
|
319
|
+
naked.clone(default_server_opts(:sql=>output(nil, [SQL::ColumnAll.new(:inserted)]).insert_sql(*values))).single_record
|
|
262
320
|
end
|
|
263
321
|
|
|
264
322
|
# Specify a table for a SELECT ... INTO query.
|
|
@@ -266,14 +324,6 @@ module Sequel
|
|
|
266
324
|
clone(:into => table)
|
|
267
325
|
end
|
|
268
326
|
|
|
269
|
-
# SQL Server does not support CTEs on subqueries, so move any CTEs
|
|
270
|
-
# on joined datasets to the top level. The user is responsible for
|
|
271
|
-
# resolving any name clashes this may cause.
|
|
272
|
-
def join_table(type, table, expr=nil, table_alias={}, &block)
|
|
273
|
-
return super unless Dataset === table && table.opts[:with]
|
|
274
|
-
clone(:with => (opts[:with] || []) + table.opts[:with]).join_table(type, table.clone(:with => nil), expr, table_alias, &block)
|
|
275
|
-
end
|
|
276
|
-
|
|
277
327
|
# MSSQL uses a UNION ALL statement to insert multiple values at once.
|
|
278
328
|
def multi_insert_sql(columns, values)
|
|
279
329
|
[insert_sql(columns, LiteralString.new(values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")))]
|
|
@@ -319,34 +369,16 @@ module Sequel
|
|
|
319
369
|
"[#{name}]"
|
|
320
370
|
end
|
|
321
371
|
|
|
322
|
-
# MSSQL Requires the use of the ROW_NUMBER window function to emulate
|
|
323
|
-
# an offset. This implementation requires MSSQL 2005 or greater (offset
|
|
324
|
-
# can't be emulated well in MSSQL 2000).
|
|
325
|
-
#
|
|
326
|
-
# The implementation is ugly, cloning the current dataset and modifying
|
|
327
|
-
# the clone to add a ROW_NUMBER window function (and some other things),
|
|
328
|
-
# then using the modified clone in a subselect which is selected from.
|
|
329
|
-
#
|
|
330
|
-
# If offset is used, an order must be provided, because the use of ROW_NUMBER
|
|
331
|
-
# requires an order.
|
|
332
|
-
def select_sql
|
|
333
|
-
return super unless o = @opts[:offset]
|
|
334
|
-
raise(Error, 'MSSQL requires an order be provided if using an offset') unless order = @opts[:order]
|
|
335
|
-
dsa1 = dataset_alias(1)
|
|
336
|
-
rn = row_number_column
|
|
337
|
-
subselect_sql(unlimited.
|
|
338
|
-
unordered.
|
|
339
|
-
select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
|
|
340
|
-
from_self(:alias=>dsa1).
|
|
341
|
-
limit(@opts[:limit]).
|
|
342
|
-
where(SQL::Identifier.new(rn) > o))
|
|
343
|
-
end
|
|
344
|
-
|
|
345
372
|
# The version of the database server.
|
|
346
373
|
def server_version
|
|
347
374
|
db.server_version(@opts[:server])
|
|
348
375
|
end
|
|
349
376
|
|
|
377
|
+
# MSSQL supports insert_select via the OUTPUT clause.
|
|
378
|
+
def supports_insert_select?
|
|
379
|
+
supports_output_clause? && !opts[:disable_insert_output]
|
|
380
|
+
end
|
|
381
|
+
|
|
350
382
|
# MSSQL 2005+ supports INTERSECT and EXCEPT
|
|
351
383
|
def supports_intersect_except?
|
|
352
384
|
is_2005_or_later?
|
|
@@ -381,6 +413,11 @@ module Sequel
|
|
|
381
413
|
def supports_window_functions?
|
|
382
414
|
true
|
|
383
415
|
end
|
|
416
|
+
|
|
417
|
+
# MSSQL cannot use WHERE 1.
|
|
418
|
+
def supports_where_true?
|
|
419
|
+
false
|
|
420
|
+
end
|
|
384
421
|
|
|
385
422
|
protected
|
|
386
423
|
# MSSQL does not allow ordering in sub-clauses unless 'top' (limit) is specified
|
|
@@ -389,6 +426,7 @@ module Sequel
|
|
|
389
426
|
end
|
|
390
427
|
|
|
391
428
|
private
|
|
429
|
+
|
|
392
430
|
def is_2005_or_later?
|
|
393
431
|
server_version >= 9000000
|
|
394
432
|
end
|
|
@@ -434,6 +472,16 @@ module Sequel
|
|
|
434
472
|
INSERT_CLAUSE_METHODS
|
|
435
473
|
end
|
|
436
474
|
|
|
475
|
+
# Use OUTPUT INSERTED.* to return all columns of the inserted row,
|
|
476
|
+
# for use with the prepared statement code.
|
|
477
|
+
def insert_output_sql(sql)
|
|
478
|
+
if @opts.has_key?(:returning)
|
|
479
|
+
sql << " OUTPUT INSERTED.*"
|
|
480
|
+
else
|
|
481
|
+
output_sql(sql)
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
437
485
|
# MSSQL uses a literal hexidecimal number for blob strings
|
|
438
486
|
def literal_blob(v)
|
|
439
487
|
blob = '0x'
|
|
@@ -441,9 +489,10 @@ module Sequel
|
|
|
441
489
|
blob
|
|
442
490
|
end
|
|
443
491
|
|
|
444
|
-
#
|
|
492
|
+
# Optionally use unicode string syntax for all strings. Don't double
|
|
493
|
+
# backslashes.
|
|
445
494
|
def literal_string(v)
|
|
446
|
-
"N'#{v.gsub(/'/, "''")}'"
|
|
495
|
+
"#{'N' if mssql_unicode_strings}'#{v.gsub(/'/, "''")}'"
|
|
447
496
|
end
|
|
448
497
|
|
|
449
498
|
# Use 0 for false on MSSQL
|
|
@@ -456,11 +505,6 @@ module Sequel
|
|
|
456
505
|
BOOL_TRUE
|
|
457
506
|
end
|
|
458
507
|
|
|
459
|
-
# The alias to use for the row_number column when emulating OFFSET
|
|
460
|
-
def row_number_column
|
|
461
|
-
:x_sequel_row_number_x
|
|
462
|
-
end
|
|
463
|
-
|
|
464
508
|
# MSSQL adds the limit before the columns
|
|
465
509
|
def select_clause_methods
|
|
466
510
|
SELECT_CLAUSE_METHODS
|
|
@@ -508,7 +552,6 @@ module Sequel
|
|
|
508
552
|
end
|
|
509
553
|
alias delete_output_sql output_sql
|
|
510
554
|
alias update_output_sql output_sql
|
|
511
|
-
alias insert_output_sql output_sql
|
|
512
555
|
|
|
513
556
|
# MSSQL supports the OUTPUT clause for UPDATE statements.
|
|
514
557
|
# It also allows prepending a WITH clause.
|