sequel 4.3.0 → 4.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +34 -0
- data/README.rdoc +7 -7
- data/Rakefile +2 -2
- data/doc/active_record.rdoc +2 -2
- data/doc/association_basics.rdoc +21 -7
- data/doc/bin_sequel.rdoc +2 -2
- data/doc/cheat_sheet.rdoc +2 -1
- data/doc/dataset_basics.rdoc +1 -1
- data/doc/dataset_filtering.rdoc +1 -1
- data/doc/migration.rdoc +2 -2
- data/doc/object_model.rdoc +2 -2
- data/doc/opening_databases.rdoc +13 -1
- data/doc/querying.rdoc +9 -4
- data/doc/release_notes/4.4.0.txt +92 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +2 -2
- data/doc/sql.rdoc +3 -3
- data/doc/thread_safety.rdoc +1 -1
- data/doc/validations.rdoc +1 -1
- data/doc/virtual_rows.rdoc +1 -1
- data/lib/sequel/adapters/jdbc.rb +85 -19
- data/lib/sequel/adapters/jdbc/db2.rb +1 -1
- data/lib/sequel/adapters/jdbc/derby.rb +1 -1
- data/lib/sequel/adapters/jdbc/h2.rb +2 -2
- data/lib/sequel/adapters/jdbc/hsqldb.rb +7 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +1 -1
- data/lib/sequel/adapters/jdbc/oracle.rb +1 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +34 -3
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +57 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +2 -2
- data/lib/sequel/adapters/oracle.rb +1 -1
- data/lib/sequel/adapters/shared/db2.rb +5 -0
- data/lib/sequel/adapters/shared/oracle.rb +41 -4
- data/lib/sequel/adapters/shared/sqlanywhere.rb +458 -0
- data/lib/sequel/adapters/sqlanywhere.rb +177 -0
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +11 -3
- data/lib/sequel/core.rb +4 -4
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +1 -1
- data/lib/sequel/database/schema_methods.rb +2 -2
- data/lib/sequel/dataset.rb +1 -1
- data/lib/sequel/dataset/actions.rb +2 -0
- data/lib/sequel/dataset/graph.rb +1 -1
- data/lib/sequel/dataset/prepared_statements.rb +1 -1
- data/lib/sequel/dataset/query.rb +37 -16
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/date_arithmetic.rb +2 -2
- data/lib/sequel/extensions/migration.rb +1 -1
- data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +5 -4
- data/lib/sequel/extensions/pg_array.rb +2 -2
- data/lib/sequel/extensions/pg_array_ops.rb +2 -2
- data/lib/sequel/extensions/pg_hstore.rb +2 -2
- data/lib/sequel/extensions/pg_hstore_ops.rb +2 -2
- data/lib/sequel/extensions/pg_json.rb +2 -2
- data/lib/sequel/extensions/pg_json_ops.rb +2 -2
- data/lib/sequel/extensions/pg_range.rb +2 -2
- data/lib/sequel/extensions/pg_range_ops.rb +2 -2
- data/lib/sequel/extensions/pg_row.rb +2 -2
- data/lib/sequel/extensions/pg_row_ops.rb +3 -3
- data/lib/sequel/model.rb +1 -1
- data/lib/sequel/model/associations.rb +106 -17
- data/lib/sequel/model/base.rb +23 -19
- data/lib/sequel/plugins/json_serializer.rb +1 -1
- data/lib/sequel/plugins/many_through_many.rb +14 -6
- data/lib/sequel/plugins/pg_array_associations.rb +28 -0
- data/lib/sequel/plugins/rcte_tree.rb +1 -1
- data/lib/sequel/plugins/serialization.rb +11 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/table_select.rb +41 -0
- data/lib/sequel/plugins/tree.rb +1 -1
- data/lib/sequel/sql.rb +2 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/oracle_spec.rb +22 -1
- data/spec/adapters/postgres_spec.rb +31 -48
- data/spec/adapters/sqlanywhere_spec.rb +170 -0
- data/spec/core/dataset_spec.rb +109 -0
- data/spec/core/object_graph_spec.rb +7 -0
- data/spec/extensions/constraint_validations_spec.rb +7 -0
- data/spec/extensions/core_refinements_spec.rb +1 -1
- data/spec/extensions/many_through_many_spec.rb +65 -0
- data/spec/extensions/pg_array_associations_spec.rb +44 -0
- data/spec/extensions/rcte_tree_spec.rb +3 -3
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/extensions/table_select_spec.rb +71 -0
- data/spec/integration/associations_test.rb +279 -7
- data/spec/integration/dataset_test.rb +13 -4
- data/spec/integration/schema_test.rb +12 -14
- data/spec/model/associations_spec.rb +472 -3
- data/spec/model/class_dataset_methods_spec.rb +1 -0
- data/spec/model/model_spec.rb +10 -0
- metadata +10 -2
@@ -291,7 +291,7 @@ module Sequel
|
|
291
291
|
:primary_key => pks.include?(column.name),
|
292
292
|
:default => defaults[column.name],
|
293
293
|
:oci8_type => column.data_type,
|
294
|
-
:db_type => column.type_string
|
294
|
+
:db_type => column.type_string,
|
295
295
|
:type_string => column.type_string,
|
296
296
|
:charset_form => column.charset_form,
|
297
297
|
:char_used => column.char_used?,
|
@@ -350,6 +350,11 @@ module Sequel
|
|
350
350
|
end
|
351
351
|
end
|
352
352
|
|
353
|
+
# DB2 does not require that ROW_NUMBER be ordered.
|
354
|
+
def require_offset_order?
|
355
|
+
false
|
356
|
+
end
|
357
|
+
|
353
358
|
# Add a fallback table for empty from situation
|
354
359
|
def select_from_sql(sql)
|
355
360
|
@opts[:from] ? super : (sql << EMPTY_FROM_TABLE)
|
@@ -31,6 +31,29 @@ module Sequel
|
|
31
31
|
:oracle
|
32
32
|
end
|
33
33
|
|
34
|
+
def foreign_key_list(table, opts=OPTS)
|
35
|
+
m = output_identifier_meth
|
36
|
+
im = input_identifier_meth
|
37
|
+
schema, table = schema_and_table(table)
|
38
|
+
ds = metadata_dataset.
|
39
|
+
from(:all_cons_columns___pc, :all_constraints___p, :all_cons_columns___fc, :all_constraints___f).
|
40
|
+
where(:f__table_name=>im.call(table), :f__constraint_type=>'R', :p__owner=>:f__r_owner, :p__constraint_name=>:f__r_constraint_name, :pc__owner=>:p__owner, :pc__constraint_name=>:p__constraint_name, :pc__table_name=>:p__table_name, :fc__owner=>:f__owner, :fc__constraint_name=>:f__constraint_name, :fc__table_name=>:f__table_name, :fc__position=>:pc__position).
|
41
|
+
select(:p__table_name___table, :pc__column_name___key, :fc__column_name___column, :f__constraint_name___name).
|
42
|
+
order(:table, :fc__position)
|
43
|
+
ds = ds.where(:f__schema_name=>im.call(schema)) if schema
|
44
|
+
|
45
|
+
fks = {}
|
46
|
+
ds.each do |r|
|
47
|
+
if fk = fks[r[:name]]
|
48
|
+
fk[:columns] << m.call(r[:column])
|
49
|
+
fk[:key] << m.call(r[:key])
|
50
|
+
else
|
51
|
+
fks[r[:name]] = {:name=>m.call(r[:name]), :columns=>[m.call(r[:column])], :table=>m.call(r[:table]), :key=>[m.call(r[:key])]}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
fks.values
|
55
|
+
end
|
56
|
+
|
34
57
|
# Oracle namespaces indexes per table.
|
35
58
|
def global_index_namespace?
|
36
59
|
false
|
@@ -38,7 +61,7 @@ module Sequel
|
|
38
61
|
|
39
62
|
def tables(opts=OPTS)
|
40
63
|
m = output_identifier_meth
|
41
|
-
metadata_dataset.from(:
|
64
|
+
metadata_dataset.from(:tabs).server(opts[:server]).select(:table_name).map{|r| m.call(r[:table_name])}
|
42
65
|
end
|
43
66
|
|
44
67
|
def views(opts=OPTS)
|
@@ -224,8 +247,6 @@ module Sequel
|
|
224
247
|
end
|
225
248
|
|
226
249
|
module DatasetMethods
|
227
|
-
include EmulateOffsetWithRowNumber
|
228
|
-
|
229
250
|
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct columns from join where group having compounds order lock')
|
230
251
|
ROW_NUMBER_EXPRESSION = LiteralString.new('ROWNUM').freeze
|
231
252
|
SPACE = Dataset::SPACE
|
@@ -326,7 +347,23 @@ module Sequel
|
|
326
347
|
|
327
348
|
# Handle LIMIT by using a unlimited subselect filtered with ROWNUM.
|
328
349
|
def select_sql
|
329
|
-
|
350
|
+
return super if @opts[:sql]
|
351
|
+
if o = @opts[:offset]
|
352
|
+
columns = clone(:append_sql=>'').columns
|
353
|
+
dsa1 = dataset_alias(1)
|
354
|
+
rn = row_number_column
|
355
|
+
limit = @opts[:limit]
|
356
|
+
ds = unlimited.
|
357
|
+
from_self(:alias=>dsa1).
|
358
|
+
select_append(ROW_NUMBER_EXPRESSION.as(rn)).
|
359
|
+
from_self(:alias=>dsa1).
|
360
|
+
select(*columns).
|
361
|
+
where(SQL::Identifier.new(rn) > o)
|
362
|
+
ds = ds.where(SQL::Identifier.new(rn) <= Sequel.+(o, limit)) if limit
|
363
|
+
sql = @opts[:append_sql] || ''
|
364
|
+
subselect_sql_append(sql, ds)
|
365
|
+
sql
|
366
|
+
elsif limit = @opts[:limit]
|
330
367
|
ds = clone(:limit=>nil)
|
331
368
|
# Lock doesn't work in subselects, so don't use a subselect when locking.
|
332
369
|
# Don't use a subselect if custom SQL is used, as it breaks somethings.
|
@@ -0,0 +1,458 @@
|
|
1
|
+
module Sequel
|
2
|
+
module SqlAnywhere
|
3
|
+
|
4
|
+
@convert_smallint_to_bool = true
|
5
|
+
|
6
|
+
class << self
|
7
|
+
# Whether to convert smallint values to bool, false by default.
|
8
|
+
# Can also be overridden per dataset.
|
9
|
+
attr_accessor :convert_smallint_to_bool
|
10
|
+
end
|
11
|
+
|
12
|
+
module DatabaseMethods
|
13
|
+
extend Sequel::Database::ResetIdentifierMangling
|
14
|
+
|
15
|
+
attr_reader :conversion_procs
|
16
|
+
|
17
|
+
# Override the default SqlAnywhere.convert_smallint_to_bool setting for this database.
|
18
|
+
attr_writer :convert_smallint_to_bool
|
19
|
+
|
20
|
+
AUTO_INCREMENT = 'IDENTITY'.freeze
|
21
|
+
SQL_BEGIN = "BEGIN TRANSACTION".freeze
|
22
|
+
SQL_COMMIT = "COMMIT TRANSACTION".freeze
|
23
|
+
SQL_ROLLBACK = "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION".freeze
|
24
|
+
TEMPORARY = "GLOBAL TEMPORARY ".freeze
|
25
|
+
SMALLINT_RE = /smallint/i.freeze
|
26
|
+
DECIMAL_TYPE_RE = /numeric/io
|
27
|
+
|
28
|
+
# Whether to convert smallint to boolean arguments for this dataset.
|
29
|
+
# Defaults to the SqlAnywhere module setting.
|
30
|
+
def convert_smallint_to_bool
|
31
|
+
defined?(@convert_smallint_to_bool) ? @convert_smallint_to_bool : (@convert_smallint_to_bool = ::Sequel::SqlAnywhere.convert_smallint_to_bool)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sysbase Server uses the :sqlanywhere type.
|
35
|
+
def database_type
|
36
|
+
:sqlanywhere
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_application_timestamp_sa(v)
|
40
|
+
to_application_timestamp(v.to_s) if v
|
41
|
+
end
|
42
|
+
|
43
|
+
# Convert smallint type to boolean if convert_smallint_to_bool is true
|
44
|
+
def schema_column_type(db_type)
|
45
|
+
if convert_smallint_to_bool && db_type =~ SMALLINT_RE
|
46
|
+
:boolean
|
47
|
+
else
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def schema_parse_table(table, opts)
|
53
|
+
m = output_identifier_meth(opts[:dataset])
|
54
|
+
im = input_identifier_meth(opts[:dataset])
|
55
|
+
metadata_dataset.
|
56
|
+
from{sa_describe_query("select * from #{im.call(table)}").as(:a)}.
|
57
|
+
join(:syscolumn___b, :table_id=>:base_table_id, :column_id=>:base_column_id).
|
58
|
+
order(:a__column_number).
|
59
|
+
map do |row|
|
60
|
+
row[:auto_increment] = row.delete(:is_autoincrement) == 1
|
61
|
+
row[:primary_key] = row.delete(:pkey) == 'Y'
|
62
|
+
row[:allow_null] = row[:nulls_allowed].is_a?(Fixnum) ? row.delete(:nulls_allowed) == 1 : row.delete(:nulls_allowed)
|
63
|
+
row[:db_type] = row.delete(:domain_name)
|
64
|
+
row[:type] = if row[:db_type] =~ DECIMAL_TYPE_RE and (row[:scale].is_a?(Fixnum) ? row[:scale] == 0 : !row[:scale])
|
65
|
+
:integer
|
66
|
+
else
|
67
|
+
schema_column_type(row[:db_type])
|
68
|
+
end
|
69
|
+
[m.call(row.delete(:name)), row]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def indexes(table, opts = OPTS)
|
74
|
+
m = output_identifier_meth
|
75
|
+
im = input_identifier_meth
|
76
|
+
indexes = {}
|
77
|
+
metadata_dataset.
|
78
|
+
from(:dbo__sysobjects___z).
|
79
|
+
select(:z__name___table_name, :i__name___index_name, :si__indextype___type, :si__colnames___columns).
|
80
|
+
join(:dbo__sysindexes___i, :id___i=> :id___z).
|
81
|
+
join(:sys__sysindexes___si, :iname=> :name___i).
|
82
|
+
where(:z__type => 'U', :table_name=>im.call(table)).
|
83
|
+
each do |r|
|
84
|
+
indexes[m.call(r[:index_name])] =
|
85
|
+
{:unique=>(r[:type].downcase=='unique'),
|
86
|
+
:columns=>r[:columns].split(',').map{|v| m.call(v.split(' ').first)}} unless r[:type].downcase == 'primary key'
|
87
|
+
end
|
88
|
+
indexes
|
89
|
+
end
|
90
|
+
|
91
|
+
def foreign_key_list(table, opts=OPTS)
|
92
|
+
m = output_identifier_meth
|
93
|
+
im = input_identifier_meth
|
94
|
+
fk_indexes = {}
|
95
|
+
metadata_dataset.
|
96
|
+
from(:sys__sysforeignkey___fk).
|
97
|
+
select(:fk__role___name, :fks__columns___column_map, :si__indextype___type, :si__colnames___columns, :fks__primary_tname___table_name).
|
98
|
+
join(:sys__sysforeignkeys___fks, :role => :role).
|
99
|
+
join_table(:inner, :sys__sysindexes___si, [:iname=> :fk__role], {:implicit_qualifier => :fk}).
|
100
|
+
where(:fks__foreign_tname=>im.call(table)).
|
101
|
+
each do |r|
|
102
|
+
unless r[:type].downcase == 'primary key'
|
103
|
+
fk_indexes[r[:name]] =
|
104
|
+
{:name=>m.call(r[:name]),
|
105
|
+
:columns=>r[:columns].split(',').map{|v| m.call(v.split(' ').first)},
|
106
|
+
:table=>m.call(r[:table_name]),
|
107
|
+
:key=>r[:column_map].split(',').map{|v| m.call(v.split(' IS ').last)}}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
fk_indexes.values
|
111
|
+
end
|
112
|
+
|
113
|
+
def tables(opts=OPTS)
|
114
|
+
tables_and_views('U', opts)
|
115
|
+
end
|
116
|
+
|
117
|
+
def views(opts=OPTS)
|
118
|
+
tables_and_views('V', opts)
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
DATABASE_ERROR_REGEXPS = {
|
124
|
+
/would not be unique/ => Sequel::UniqueConstraintViolation,
|
125
|
+
/Column .* in table .* cannot be NULL/ => Sequel::NotNullConstraintViolation,
|
126
|
+
/Constraint .* violated: Invalid value in table .*/ => Sequel::CheckConstraintViolation,
|
127
|
+
/No primary key value for foreign key .* in table .*/ => Sequel::ForeignKeyConstraintViolation,
|
128
|
+
/Primary key for row in table .* is referenced by foreign key .* in table .*/ => Sequel::ForeignKeyConstraintViolation
|
129
|
+
}.freeze
|
130
|
+
|
131
|
+
def database_error_regexps
|
132
|
+
DATABASE_ERROR_REGEXPS
|
133
|
+
end
|
134
|
+
|
135
|
+
# Sybase uses the IDENTITY column for autoincrementing columns.
|
136
|
+
def auto_increment_sql
|
137
|
+
AUTO_INCREMENT
|
138
|
+
end
|
139
|
+
|
140
|
+
# SQL fragment for marking a table as temporary
|
141
|
+
def temporary_table_sql
|
142
|
+
TEMPORARY
|
143
|
+
end
|
144
|
+
|
145
|
+
# SQL to BEGIN a transaction.
|
146
|
+
def begin_transaction_sql
|
147
|
+
SQL_BEGIN
|
148
|
+
end
|
149
|
+
|
150
|
+
# SQL to ROLLBACK a transaction.
|
151
|
+
def rollback_transaction_sql
|
152
|
+
SQL_ROLLBACK
|
153
|
+
end
|
154
|
+
|
155
|
+
# SQL to COMMIT a transaction.
|
156
|
+
def commit_transaction_sql
|
157
|
+
SQL_COMMIT
|
158
|
+
end
|
159
|
+
|
160
|
+
# Sybase has both datetime and timestamp classes, most people are going
|
161
|
+
# to want datetime
|
162
|
+
def type_literal_generic_datetime(column)
|
163
|
+
:datetime
|
164
|
+
end
|
165
|
+
|
166
|
+
# Sybase has both datetime and timestamp classes, most people are going
|
167
|
+
# to want datetime
|
168
|
+
def type_literal_generic_time(column)
|
169
|
+
column[:only_time] ? :time : :datetime
|
170
|
+
end
|
171
|
+
|
172
|
+
# Sybase doesn't have a true boolean class, so it uses integer
|
173
|
+
def type_literal_generic_trueclass(column)
|
174
|
+
:smallint
|
175
|
+
end
|
176
|
+
|
177
|
+
# SQLAnywhere uses image type for blobs
|
178
|
+
def type_literal_generic_file(column)
|
179
|
+
:image
|
180
|
+
end
|
181
|
+
|
182
|
+
# Sybase specific syntax for altering tables.
|
183
|
+
def alter_table_sql(table, op)
|
184
|
+
case op[:op]
|
185
|
+
when :add_column
|
186
|
+
"ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
|
187
|
+
when :drop_column
|
188
|
+
"ALTER TABLE #{quote_schema_table(table)} DROP #{column_definition_sql(op)}"
|
189
|
+
when :drop_constraint
|
190
|
+
case op[:type]
|
191
|
+
when :primary_key
|
192
|
+
"ALTER TABLE #{quote_schema_table(table)} DROP PRIMARY KEY"
|
193
|
+
when :foreign_key
|
194
|
+
if op[:name] || op[:columns]
|
195
|
+
name = op[:name] || foreign_key_name(table, op[:columns])
|
196
|
+
if name
|
197
|
+
"ALTER TABLE #{quote_schema_table(table)} DROP FOREIGN KEY #{quote_identifier(name)}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
else
|
201
|
+
super
|
202
|
+
end
|
203
|
+
when :rename_column
|
204
|
+
"ALTER TABLE #{quote_schema_table(table)} RENAME #{quote_identifier(op[:name])} TO #{quote_identifier(op[:new_name].to_s)}"
|
205
|
+
when :set_column_type
|
206
|
+
"ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} #{type_literal(op)}"
|
207
|
+
when :set_column_null
|
208
|
+
"ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} #{'NOT ' unless op[:null]}NULL"
|
209
|
+
when :set_column_default
|
210
|
+
"ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} DEFAULT #{literal(op[:default])}"
|
211
|
+
else
|
212
|
+
super(table, op)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# SqlAnywhere doesn't support CREATE TABLE AS, it only supports SELECT INTO.
|
217
|
+
# Emulating CREATE TABLE AS using SELECT INTO is only possible if a dataset
|
218
|
+
# is given as the argument, it can't work with a string, so raise an
|
219
|
+
# Error if a string is given.
|
220
|
+
def create_table_as(name, ds, options)
|
221
|
+
raise(Error, "must provide dataset instance as value of create_table :as option on SqlAnywhere") unless ds.is_a?(Sequel::Dataset)
|
222
|
+
run(ds.into(name).sql)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Use SP_RENAME to rename the table
|
226
|
+
def rename_table_sql(name, new_name)
|
227
|
+
"ALTER TABLE #{quote_schema_table(name)} RENAME #{quote_schema_table(new_name)}"
|
228
|
+
end
|
229
|
+
|
230
|
+
def tables_and_views(type, opts=OPTS)
|
231
|
+
m = output_identifier_meth
|
232
|
+
metadata_dataset.
|
233
|
+
from(:sysobjects___a).
|
234
|
+
where(:a__type=>type).
|
235
|
+
select_map(:a__name).
|
236
|
+
map{|n| m.call(n)}
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
module DatasetMethods
|
241
|
+
BOOL_TRUE = '1'.freeze
|
242
|
+
BOOL_FALSE = '0'.freeze
|
243
|
+
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with insert into columns values')
|
244
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct limit columns into from join where group having order compounds lock')
|
245
|
+
WILDCARD = LiteralString.new('%').freeze
|
246
|
+
TOP = " TOP ".freeze
|
247
|
+
START_AT = " START AT ".freeze
|
248
|
+
SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
|
249
|
+
DATE_FUNCTION = 'today()'.freeze
|
250
|
+
NOW_FUNCTION = 'now()'.freeze
|
251
|
+
DATEPART = 'datepart'.freeze
|
252
|
+
REGEXP = 'REGEXP'.freeze
|
253
|
+
NOT_REGEXP = 'NOT REGEXP'.freeze
|
254
|
+
TIMESTAMP_USEC_FORMAT = ".%03d".freeze
|
255
|
+
APOS = Dataset::APOS
|
256
|
+
APOS_RE = Dataset::APOS_RE
|
257
|
+
DOUBLE_APOS = Dataset::DOUBLE_APOS
|
258
|
+
BACKSLASH_RE = /\\/.freeze
|
259
|
+
QUAD_BACKSLASH = "\\\\\\\\".freeze
|
260
|
+
BLOB_START = "0x".freeze
|
261
|
+
HSTAR = "H*".freeze
|
262
|
+
CROSS_APPLY = 'CROSS APPLY'.freeze
|
263
|
+
OUTER_APPLY = 'OUTER APPLY'.freeze
|
264
|
+
|
265
|
+
# Whether to convert smallint to boolean arguments for this dataset.
|
266
|
+
# Defaults to the SqlAnywhere module setting.
|
267
|
+
def convert_smallint_to_bool
|
268
|
+
defined?(@convert_smallint_to_bool) ? @convert_smallint_to_bool : (@convert_smallint_to_bool = @db.convert_smallint_to_bool)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Override the default SqlAnywhere.convert_smallint_to_bool setting for this dataset.
|
272
|
+
attr_writer :convert_smallint_to_bool
|
273
|
+
|
274
|
+
def supports_multiple_column_in?
|
275
|
+
false
|
276
|
+
end
|
277
|
+
|
278
|
+
def supports_where_true?
|
279
|
+
false
|
280
|
+
end
|
281
|
+
|
282
|
+
def supports_is_true?
|
283
|
+
false
|
284
|
+
end
|
285
|
+
|
286
|
+
def supports_join_using?
|
287
|
+
false
|
288
|
+
end
|
289
|
+
|
290
|
+
def supports_timestamp_usecs?
|
291
|
+
false
|
292
|
+
end
|
293
|
+
|
294
|
+
# Uses CROSS APPLY to join the given table into the current dataset.
|
295
|
+
def cross_apply(table)
|
296
|
+
join_table(:cross_apply, table)
|
297
|
+
end
|
298
|
+
|
299
|
+
# SqlAnywhere requires recursive CTEs to have column aliases.
|
300
|
+
def recursive_cte_requires_column_aliases?
|
301
|
+
true
|
302
|
+
end
|
303
|
+
|
304
|
+
# SQLAnywhere uses + for string concatenation, and LIKE is case insensitive by default.
|
305
|
+
def complex_expression_sql_append(sql, op, args)
|
306
|
+
case op
|
307
|
+
when :'||'
|
308
|
+
super(sql, :+, args)
|
309
|
+
when :<<
|
310
|
+
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
|
311
|
+
when :>>
|
312
|
+
sql << complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
|
313
|
+
when :LIKE, :"NOT LIKE"
|
314
|
+
sql << Sequel::Dataset::PAREN_OPEN
|
315
|
+
literal_append(sql, args.at(0))
|
316
|
+
sql << Sequel::Dataset::SPACE << (op == :LIKE ? REGEXP : NOT_REGEXP) << Sequel::Dataset::SPACE
|
317
|
+
pattern = ''
|
318
|
+
last_c = ''
|
319
|
+
args.at(1).each_char do |c|
|
320
|
+
if c == '_' and not pattern.end_with?('\\') and last_c != '\\'
|
321
|
+
pattern << '.'
|
322
|
+
elsif c == '%' and not pattern.end_with?('\\') and last_c != '\\'
|
323
|
+
pattern << '.*'
|
324
|
+
elsif c == '[' and not pattern.end_with?('\\') and last_c != '\\'
|
325
|
+
pattern << '\['
|
326
|
+
elsif c == ']' and not pattern.end_with?('\\') and last_c != '\\'
|
327
|
+
pattern << '\]'
|
328
|
+
elsif c == '*' and not pattern.end_with?('\\') and last_c != '\\'
|
329
|
+
pattern << '\*'
|
330
|
+
elsif c == '?' and not pattern.end_with?('\\') and last_c != '\\'
|
331
|
+
pattern << '\?'
|
332
|
+
else
|
333
|
+
pattern << c
|
334
|
+
end
|
335
|
+
if c == '\\' and last_c == '\\'
|
336
|
+
last_c = ''
|
337
|
+
else
|
338
|
+
last_c = c
|
339
|
+
end
|
340
|
+
end
|
341
|
+
literal_append(sql, pattern)
|
342
|
+
sql << Sequel::Dataset::ESCAPE
|
343
|
+
literal_append(sql, Sequel::Dataset::BACKSLASH)
|
344
|
+
sql << Sequel::Dataset::PAREN_CLOSE
|
345
|
+
when :ILIKE, :"NOT ILIKE"
|
346
|
+
super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args)
|
347
|
+
when :extract
|
348
|
+
sql << DATEPART + Sequel::Dataset::PAREN_OPEN
|
349
|
+
literal_append(sql, args.at(0))
|
350
|
+
sql << ','
|
351
|
+
literal_append(sql, args.at(1))
|
352
|
+
sql << Sequel::Dataset::PAREN_CLOSE
|
353
|
+
else
|
354
|
+
super
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# SqlAnywhere uses \\ to escape metacharacters, but a ']' should not be escaped
|
359
|
+
def escape_like(string)
|
360
|
+
string.gsub(/[\\%_\[]/){|m| "\\#{m}"}
|
361
|
+
end
|
362
|
+
|
363
|
+
# Use Date() and Now() for CURRENT_DATE and CURRENT_TIMESTAMP
|
364
|
+
def constant_sql_append(sql, constant)
|
365
|
+
case constant
|
366
|
+
when :CURRENT_DATE
|
367
|
+
sql << DATE_FUNCTION
|
368
|
+
when :CURRENT_TIMESTAMP, :CURRENT_TIME
|
369
|
+
sql << NOW_FUNCTION
|
370
|
+
else
|
371
|
+
super
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
# Specify a table for a SELECT ... INTO query.
|
376
|
+
def into(table)
|
377
|
+
clone(:into => table)
|
378
|
+
end
|
379
|
+
|
380
|
+
private
|
381
|
+
|
382
|
+
# Use 1 for true on Sybase
|
383
|
+
def literal_true
|
384
|
+
BOOL_TRUE
|
385
|
+
end
|
386
|
+
|
387
|
+
# Use 0 for false on Sybase
|
388
|
+
def literal_false
|
389
|
+
BOOL_FALSE
|
390
|
+
end
|
391
|
+
|
392
|
+
# SQL fragment for String. Doubles \ and ' by default.
|
393
|
+
def literal_string_append(sql, v)
|
394
|
+
sql << APOS << v.gsub(BACKSLASH_RE, QUAD_BACKSLASH).gsub(APOS_RE, DOUBLE_APOS) << APOS
|
395
|
+
end
|
396
|
+
|
397
|
+
# SqlAnywhere uses a preceding X for hex escaping strings
|
398
|
+
def literal_blob_append(sql, v)
|
399
|
+
if v.empty?
|
400
|
+
literal_append(sql, "")
|
401
|
+
else
|
402
|
+
sql << BLOB_START << v.unpack(HSTAR).first
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
# Sybase supports the OUTPUT clause for INSERT statements.
|
407
|
+
# It also allows prepending a WITH clause.
|
408
|
+
def insert_clause_methods
|
409
|
+
INSERT_CLAUSE_METHODS
|
410
|
+
end
|
411
|
+
|
412
|
+
def select_clause_methods
|
413
|
+
SELECT_CLAUSE_METHODS
|
414
|
+
end
|
415
|
+
|
416
|
+
def select_into_sql(sql)
|
417
|
+
if i = @opts[:into]
|
418
|
+
sql << Sequel::Dataset::INTO
|
419
|
+
identifier_append(sql, i)
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
def format_timestamp_usec(usec)
|
424
|
+
sprintf(TIMESTAMP_USEC_FORMAT, usec/1000)
|
425
|
+
end
|
426
|
+
|
427
|
+
# Sybase uses TOP N for limit. For Sybase TOP (N) is used
|
428
|
+
# to allow the limit to be a bound variable.
|
429
|
+
def select_limit_sql(sql)
|
430
|
+
if l = @opts[:limit]
|
431
|
+
sql << TOP
|
432
|
+
literal_append(sql, l)
|
433
|
+
end
|
434
|
+
if o = @opts[:offset]
|
435
|
+
sql << START_AT + "("
|
436
|
+
literal_append(sql, o)
|
437
|
+
sql << " + 1)"
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
# Use WITH RECURSIVE instead of WITH if any of the CTEs is recursive
|
442
|
+
def select_with_sql_base
|
443
|
+
opts[:with].any?{|w| w[:recursive]} ? SQL_WITH_RECURSIVE : super
|
444
|
+
end
|
445
|
+
|
446
|
+
def join_type_sql(join_type)
|
447
|
+
case join_type
|
448
|
+
when :cross_apply
|
449
|
+
CROSS_APPLY
|
450
|
+
when :outer_apply
|
451
|
+
OUTER_APPLY
|
452
|
+
else
|
453
|
+
super
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|