sequel 4.3.0 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +34 -0
  3. data/README.rdoc +7 -7
  4. data/Rakefile +2 -2
  5. data/doc/active_record.rdoc +2 -2
  6. data/doc/association_basics.rdoc +21 -7
  7. data/doc/bin_sequel.rdoc +2 -2
  8. data/doc/cheat_sheet.rdoc +2 -1
  9. data/doc/dataset_basics.rdoc +1 -1
  10. data/doc/dataset_filtering.rdoc +1 -1
  11. data/doc/migration.rdoc +2 -2
  12. data/doc/object_model.rdoc +2 -2
  13. data/doc/opening_databases.rdoc +13 -1
  14. data/doc/querying.rdoc +9 -4
  15. data/doc/release_notes/4.4.0.txt +92 -0
  16. data/doc/schema_modification.rdoc +1 -1
  17. data/doc/security.rdoc +2 -2
  18. data/doc/sql.rdoc +3 -3
  19. data/doc/thread_safety.rdoc +1 -1
  20. data/doc/validations.rdoc +1 -1
  21. data/doc/virtual_rows.rdoc +1 -1
  22. data/lib/sequel/adapters/jdbc.rb +85 -19
  23. data/lib/sequel/adapters/jdbc/db2.rb +1 -1
  24. data/lib/sequel/adapters/jdbc/derby.rb +1 -1
  25. data/lib/sequel/adapters/jdbc/h2.rb +2 -2
  26. data/lib/sequel/adapters/jdbc/hsqldb.rb +7 -0
  27. data/lib/sequel/adapters/jdbc/jtds.rb +1 -1
  28. data/lib/sequel/adapters/jdbc/oracle.rb +1 -1
  29. data/lib/sequel/adapters/jdbc/postgresql.rb +34 -3
  30. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +57 -0
  31. data/lib/sequel/adapters/jdbc/sqlserver.rb +2 -2
  32. data/lib/sequel/adapters/oracle.rb +1 -1
  33. data/lib/sequel/adapters/shared/db2.rb +5 -0
  34. data/lib/sequel/adapters/shared/oracle.rb +41 -4
  35. data/lib/sequel/adapters/shared/sqlanywhere.rb +458 -0
  36. data/lib/sequel/adapters/sqlanywhere.rb +177 -0
  37. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +11 -3
  38. data/lib/sequel/core.rb +4 -4
  39. data/lib/sequel/database/connecting.rb +1 -1
  40. data/lib/sequel/database/query.rb +1 -1
  41. data/lib/sequel/database/schema_generator.rb +1 -1
  42. data/lib/sequel/database/schema_methods.rb +2 -2
  43. data/lib/sequel/dataset.rb +1 -1
  44. data/lib/sequel/dataset/actions.rb +2 -0
  45. data/lib/sequel/dataset/graph.rb +1 -1
  46. data/lib/sequel/dataset/prepared_statements.rb +1 -1
  47. data/lib/sequel/dataset/query.rb +37 -16
  48. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  49. data/lib/sequel/extensions/date_arithmetic.rb +2 -2
  50. data/lib/sequel/extensions/migration.rb +1 -1
  51. data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +5 -4
  52. data/lib/sequel/extensions/pg_array.rb +2 -2
  53. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  54. data/lib/sequel/extensions/pg_hstore.rb +2 -2
  55. data/lib/sequel/extensions/pg_hstore_ops.rb +2 -2
  56. data/lib/sequel/extensions/pg_json.rb +2 -2
  57. data/lib/sequel/extensions/pg_json_ops.rb +2 -2
  58. data/lib/sequel/extensions/pg_range.rb +2 -2
  59. data/lib/sequel/extensions/pg_range_ops.rb +2 -2
  60. data/lib/sequel/extensions/pg_row.rb +2 -2
  61. data/lib/sequel/extensions/pg_row_ops.rb +3 -3
  62. data/lib/sequel/model.rb +1 -1
  63. data/lib/sequel/model/associations.rb +106 -17
  64. data/lib/sequel/model/base.rb +23 -19
  65. data/lib/sequel/plugins/json_serializer.rb +1 -1
  66. data/lib/sequel/plugins/many_through_many.rb +14 -6
  67. data/lib/sequel/plugins/pg_array_associations.rb +28 -0
  68. data/lib/sequel/plugins/rcte_tree.rb +1 -1
  69. data/lib/sequel/plugins/serialization.rb +11 -0
  70. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  71. data/lib/sequel/plugins/table_select.rb +41 -0
  72. data/lib/sequel/plugins/tree.rb +1 -1
  73. data/lib/sequel/sql.rb +2 -2
  74. data/lib/sequel/version.rb +1 -1
  75. data/spec/adapters/oracle_spec.rb +22 -1
  76. data/spec/adapters/postgres_spec.rb +31 -48
  77. data/spec/adapters/sqlanywhere_spec.rb +170 -0
  78. data/spec/core/dataset_spec.rb +109 -0
  79. data/spec/core/object_graph_spec.rb +7 -0
  80. data/spec/extensions/constraint_validations_spec.rb +7 -0
  81. data/spec/extensions/core_refinements_spec.rb +1 -1
  82. data/spec/extensions/many_through_many_spec.rb +65 -0
  83. data/spec/extensions/pg_array_associations_spec.rb +44 -0
  84. data/spec/extensions/rcte_tree_spec.rb +3 -3
  85. data/spec/extensions/spec_helper.rb +1 -1
  86. data/spec/extensions/table_select_spec.rb +71 -0
  87. data/spec/integration/associations_test.rb +279 -7
  88. data/spec/integration/dataset_test.rb +13 -4
  89. data/spec/integration/schema_test.rb +12 -14
  90. data/spec/model/associations_spec.rb +472 -3
  91. data/spec/model/class_dataset_methods_spec.rb +1 -0
  92. data/spec/model/model_spec.rb +10 -0
  93. 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.split(' ')[0],
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(:tab).server(opts[:server]).select(:tname).filter(:tabtype => 'TABLE').map{|r| m.call(r[:tname])}
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
- if (limit = @opts[:limit]) && !@opts[:sql]
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