sequel 4.3.0 → 4.4.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.
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