sequel 3.39.0 → 3.40.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 (52) hide show
  1. data/CHANGELOG +30 -0
  2. data/README.rdoc +4 -3
  3. data/doc/active_record.rdoc +1 -1
  4. data/doc/opening_databases.rdoc +7 -0
  5. data/doc/release_notes/3.40.0.txt +73 -0
  6. data/lib/sequel/adapters/ado.rb +29 -3
  7. data/lib/sequel/adapters/ado/access.rb +334 -0
  8. data/lib/sequel/adapters/ado/mssql.rb +0 -6
  9. data/lib/sequel/adapters/cubrid.rb +143 -0
  10. data/lib/sequel/adapters/jdbc.rb +26 -18
  11. data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
  12. data/lib/sequel/adapters/jdbc/derby.rb +7 -7
  13. data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
  14. data/lib/sequel/adapters/jdbc/mysql.rb +9 -4
  15. data/lib/sequel/adapters/mysql.rb +0 -3
  16. data/lib/sequel/adapters/mysql2.rb +0 -3
  17. data/lib/sequel/adapters/oracle.rb +4 -1
  18. data/lib/sequel/adapters/postgres.rb +4 -4
  19. data/lib/sequel/adapters/shared/access.rb +205 -3
  20. data/lib/sequel/adapters/shared/cubrid.rb +216 -0
  21. data/lib/sequel/adapters/shared/db2.rb +7 -2
  22. data/lib/sequel/adapters/shared/mssql.rb +3 -34
  23. data/lib/sequel/adapters/shared/mysql.rb +4 -33
  24. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +11 -0
  25. data/lib/sequel/adapters/shared/oracle.rb +5 -0
  26. data/lib/sequel/adapters/shared/postgres.rb +2 -1
  27. data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
  28. data/lib/sequel/database/connecting.rb +1 -1
  29. data/lib/sequel/database/query.rb +30 -7
  30. data/lib/sequel/database/schema_methods.rb +7 -2
  31. data/lib/sequel/dataset/query.rb +9 -10
  32. data/lib/sequel/dataset/sql.rb +14 -26
  33. data/lib/sequel/extensions/pg_hstore.rb +19 -0
  34. data/lib/sequel/extensions/pg_row.rb +5 -5
  35. data/lib/sequel/plugins/association_pks.rb +121 -18
  36. data/lib/sequel/plugins/json_serializer.rb +19 -0
  37. data/lib/sequel/sql.rb +11 -0
  38. data/lib/sequel/version.rb +1 -1
  39. data/spec/adapters/postgres_spec.rb +42 -0
  40. data/spec/core/database_spec.rb +17 -0
  41. data/spec/core/dataset_spec.rb +11 -0
  42. data/spec/core/expression_filters_spec.rb +13 -0
  43. data/spec/extensions/association_pks_spec.rb +163 -3
  44. data/spec/extensions/pg_hstore_spec.rb +6 -0
  45. data/spec/extensions/pg_row_spec.rb +17 -0
  46. data/spec/integration/associations_test.rb +1 -1
  47. data/spec/integration/dataset_test.rb +13 -13
  48. data/spec/integration/plugin_test.rb +232 -7
  49. data/spec/integration/schema_test.rb +8 -12
  50. data/spec/integration/spec_helper.rb +1 -1
  51. data/spec/integration/type_test.rb +6 -0
  52. metadata +9 -2
@@ -11,12 +11,6 @@ module Sequel
11
11
  # delete query.
12
12
  ROWS_AFFECTED = "SELECT @@ROWCOUNT AS AffectedRows"
13
13
 
14
- # Just execute so it doesn't attempt to return the number of rows modified.
15
- def execute_ddl(sql, opts={})
16
- execute(sql, opts)
17
- end
18
- alias execute_insert execute_ddl
19
-
20
14
  # Issue a separate query to get the rows modified. ADO appears to
21
15
  # use pass by reference with an integer variable, which is obviously
22
16
  # not supported directly in ruby, and I'm not aware of a workaround.
@@ -0,0 +1,143 @@
1
+ require 'cubrid'
2
+ Sequel.require 'adapters/shared/cubrid'
3
+
4
+ module Sequel
5
+ module Cubrid
6
+ CUBRID_TYPE_PROCS = {
7
+ ::Cubrid::DATE => lambda{|t| Date.new(t.year, t.month, t.day)},
8
+ ::Cubrid::TIME => lambda{|t| SQLTime.create(t.hour, t.min, t.sec)},
9
+ 21 => lambda{|s| s.to_i}
10
+ }
11
+
12
+ class Database < Sequel::Database
13
+ include Sequel::Cubrid::DatabaseMethods
14
+
15
+ ROW_COUNT = "SELECT ROW_COUNT()".freeze
16
+ LAST_INSERT_ID = "SELECT LAST_INSERT_ID()".freeze
17
+
18
+ set_adapter_scheme :cubrid
19
+
20
+ def connect(server)
21
+ opts = server_opts(server)
22
+ conn = ::Cubrid.connect(
23
+ opts[:database],
24
+ opts[:host] || 'localhost',
25
+ opts[:port] || 30000,
26
+ opts[:user] || 'public',
27
+ opts[:password] || ''
28
+ )
29
+ conn.auto_commit = true
30
+ conn
31
+ end
32
+
33
+ def server_version
34
+ @server_version ||= synchronize{|c| c.server_version}
35
+ end
36
+
37
+ def execute(sql, opts={})
38
+ synchronize(opts[:server]) do |conn|
39
+ r = log_yield(sql) do
40
+ begin
41
+ conn.query(sql)
42
+ rescue => e
43
+ raise_error(e)
44
+ end
45
+ end
46
+ if block_given?
47
+ yield(r)
48
+ else
49
+ begin
50
+ case opts[:type]
51
+ when :dui
52
+ # This is cubrid's API, but it appears to be completely broken,
53
+ # giving StandardError: ERROR: CCI, -18, Invalid request handle
54
+ #r.affected_rows
55
+
56
+ # Work around bugs by using the ROW_COUNT function.
57
+ begin
58
+ r2 = conn.query(ROW_COUNT)
59
+ r2.each{|a| return a.first.to_i}
60
+ ensure
61
+ r2.close if r2
62
+ end
63
+ when :insert
64
+ begin
65
+ r2 = conn.query(LAST_INSERT_ID)
66
+ r2.each{|a| return a.first.to_i}
67
+ ensure
68
+ r2.close if r2
69
+ end
70
+ end
71
+ ensure
72
+ r.close
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def execute_ddl(sql, opts={})
79
+ execute(sql, opts.merge(:type=>:ddl))
80
+ end
81
+
82
+ def execute_dui(sql, opts={})
83
+ execute(sql, opts.merge(:type=>:dui))
84
+ end
85
+
86
+ def execute_insert(sql, opts={})
87
+ execute(sql, opts.merge(:type=>:insert))
88
+ end
89
+
90
+ private
91
+
92
+ def begin_transaction(conn, opts={})
93
+ log_yield(TRANSACTION_BEGIN){conn.auto_commit = false}
94
+ end
95
+
96
+ def commit_transaction(conn, opts={})
97
+ log_yield(TRANSACTION_COMMIT){conn.commit}
98
+ end
99
+
100
+ def disconnect_connection(c)
101
+ c.close
102
+ end
103
+
104
+ def remove_transaction(conn, committed)
105
+ conn.auto_commit = true
106
+ ensure
107
+ super
108
+ end
109
+
110
+ # This doesn't actually work, as the cubrid ruby driver
111
+ # does not implement transactions correctly.
112
+ def rollback_transaction(conn, opts={})
113
+ log_yield(TRANSACTION_ROLLBACK){conn.rollback}
114
+ end
115
+ end
116
+
117
+ class Dataset < Sequel::Dataset
118
+ include Sequel::Cubrid::DatasetMethods
119
+ COLUMN_INFO_NAME = "name".freeze
120
+ COLUMN_INFO_TYPE = "type_name".freeze
121
+
122
+ Database::DatasetClass = self
123
+
124
+ def fetch_rows(sql)
125
+ execute(sql) do |stmt|
126
+ begin
127
+ procs =
128
+ cols = stmt.column_info.map{|c| [output_identifier(c[COLUMN_INFO_NAME]), CUBRID_TYPE_PROCS[c[COLUMN_INFO_TYPE]]]}
129
+ @columns = cols.map{|c| c.first}
130
+ stmt.each do |r|
131
+ row = {}
132
+ cols.zip(r).each{|(k, p), v| row[k] = (v && p) ? p.call(v) : v}
133
+ yield row
134
+ end
135
+ ensure
136
+ stmt.close
137
+ end
138
+ end
139
+ self
140
+ end
141
+ end
142
+ end
143
+ end
@@ -122,6 +122,12 @@ module Sequel
122
122
  db.extend(Sequel::JDBC::Progress::DatabaseMethods)
123
123
  db.extend_datasets Sequel::Progress::DatasetMethods
124
124
  com.progress.sql.jdbc.JdbcProgressDriver
125
+ end,
126
+ :cubrid=>proc do |db|
127
+ Sequel.ts_require 'adapters/jdbc/cubrid'
128
+ db.extend(Sequel::JDBC::Cubrid::DatabaseMethods)
129
+ db.extend_datasets Sequel::Cubrid::DatasetMethods
130
+ Java::cubrid.jdbc.driver.CUBRIDDriver
125
131
  end
126
132
  }
127
133
 
@@ -248,13 +254,7 @@ module Sequel
248
254
  when :ddl
249
255
  log_yield(sql){stmt.execute(sql)}
250
256
  when :insert
251
- log_yield(sql) do
252
- if requires_return_generated_keys?
253
- stmt.executeUpdate(sql, JavaSQL::Statement.RETURN_GENERATED_KEYS)
254
- else
255
- stmt.executeUpdate(sql)
256
- end
257
- end
257
+ log_yield(sql){execute_statement_insert(stmt, sql)}
258
258
  last_insert_id(conn, opts.merge(:stmt=>stmt))
259
259
  else
260
260
  log_yield(sql){stmt.executeUpdate(sql)}
@@ -264,7 +264,7 @@ module Sequel
264
264
  end
265
265
  end
266
266
  alias execute_dui execute
267
-
267
+
268
268
  # Execute the given DDL SQL, which should not return any
269
269
  # values or rows.
270
270
  def execute_ddl(sql, opts={})
@@ -361,7 +361,7 @@ module Sequel
361
361
  cps = cps[1]
362
362
  else
363
363
  log_yield("CLOSE #{name}"){cps[1].close} if cps
364
- cps = log_yield("PREPARE#{" #{name}:" if name} #{sql}"){conn.prepareStatement(sql)}
364
+ cps = log_yield("PREPARE#{" #{name}:" if name} #{sql}"){prepare_jdbc_statement(conn, sql, opts)}
365
365
  cps_sync(conn){|cpsh| cpsh[name] = [sql, cps]} if name
366
366
  end
367
367
  i = 0
@@ -380,8 +380,8 @@ module Sequel
380
380
  when :ddl
381
381
  log_yield(msg, args){cps.execute}
382
382
  when :insert
383
- log_yield(msg, args){cps.executeUpdate}
384
- last_insert_id(conn, opts.merge(:prepared=>true))
383
+ log_yield(msg, args){execute_prepared_statement_insert(cps)}
384
+ last_insert_id(conn, opts.merge(:prepared=>true, :stmt=>cps))
385
385
  else
386
386
  log_yield(msg, args){cps.executeUpdate}
387
387
  end
@@ -394,6 +394,16 @@ module Sequel
394
394
  end
395
395
  end
396
396
 
397
+ # Execute the prepared insert statement
398
+ def execute_prepared_statement_insert(stmt)
399
+ stmt.executeUpdate
400
+ end
401
+
402
+ # Execute the insert SQL using the statement
403
+ def execute_statement_insert(stmt, sql)
404
+ stmt.executeUpdate(sql)
405
+ end
406
+
397
407
  # Gets the connection from JNDI.
398
408
  def get_connection_from_jndi
399
409
  jndi_name = JNDI_URI_REGEXP.match(uri)[1]
@@ -461,6 +471,11 @@ module Sequel
461
471
  end
462
472
  end
463
473
 
474
+ # Created a JDBC prepared statement on the connection with the given SQL.
475
+ def prepare_jdbc_statement(conn, sql, opts)
476
+ conn.prepareStatement(sql)
477
+ end
478
+
464
479
  # Java being java, you need to specify the type of each argument
465
480
  # for the prepared statement, and bind it individually. This
466
481
  # guesses which JDBC method to use, and hopefully JRuby will convert
@@ -545,13 +560,6 @@ module Sequel
545
560
  ensure
546
561
  stmt.close if stmt
547
562
  end
548
-
549
- # This method determines whether or not to add
550
- # Statement.RETURN_GENERATED_KEYS as an argument when inserting rows.
551
- # Sub-adapters that require this should override this method.
552
- def requires_return_generated_keys?
553
- false
554
- end
555
563
  end
556
564
 
557
565
  class Dataset < Sequel::Dataset
@@ -0,0 +1,52 @@
1
+ Sequel.require 'adapters/shared/cubrid'
2
+ Sequel.require 'adapters/jdbc/transactions'
3
+
4
+ module Sequel
5
+ module JDBC
6
+ module Cubrid
7
+ module DatabaseMethods
8
+ include Sequel::Cubrid::DatabaseMethods
9
+ include Sequel::JDBC::Transactions
10
+
11
+ def supports_savepoints?
12
+ false
13
+ end
14
+
15
+ private
16
+
17
+ # Get the last inserted id using LAST_INSERT_ID().
18
+ def last_insert_id(conn, opts={})
19
+ if stmt = opts[:stmt]
20
+ rs = stmt.getGeneratedKeys
21
+ begin
22
+ if rs.next
23
+ rs.getInt(1)
24
+ end
25
+ rescue NativeException
26
+ nil
27
+ ensure
28
+ rs.close
29
+ end
30
+ end
31
+ end
32
+
33
+ # Use execute instead of executeUpdate.
34
+ def execute_prepared_statement_insert(stmt)
35
+ stmt.execute
36
+ end
37
+
38
+ # Return generated keys for insert statements, and use
39
+ # execute intead of executeUpdate as CUBRID doesn't
40
+ # return generated keys in executeUpdate.
41
+ def execute_statement_insert(stmt, sql)
42
+ stmt.execute(sql, JavaSQL::Statement.RETURN_GENERATED_KEYS)
43
+ end
44
+
45
+ # Return generated keys for insert statements.
46
+ def prepare_jdbc_statement(conn, sql, opts)
47
+ opts[:type] == :insert ? conn.prepareStatement(sql, JavaSQL::Statement.RETURN_GENERATED_KEYS) : super
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -148,6 +148,11 @@ module Sequel
148
148
  super
149
149
  end
150
150
  end
151
+
152
+ # Derby uses clob for text types.
153
+ def uses_clob_for_text?
154
+ true
155
+ end
151
156
  end
152
157
 
153
158
  # Dataset class for Derby datasets accessed via JDBC.
@@ -174,15 +179,10 @@ module Sequel
174
179
  EMULATED_FUNCTION_MAP = {:char_length=>'length'.freeze}
175
180
 
176
181
  # Derby doesn't support an expression between CASE and WHEN,
177
- # so emulate it by using an equality statement for all of the
182
+ # so remove
178
183
  # conditions.
179
184
  def case_expression_sql_append(sql, ce)
180
- if ce.expression?
181
- e = ce.expression
182
- case_expression_sql_append(sql, ::Sequel::SQL::CaseExpression.new(ce.conditions.map{|c, r| [::Sequel::SQL::BooleanExpression.new(:'=', e, c), r]}, ce.default))
183
- else
184
- super
185
- end
185
+ super(sql, ce.with_merged_expression)
186
186
  end
187
187
 
188
188
  # If the type is String, trim the extra spaces since CHAR is used instead
@@ -83,6 +83,11 @@ module Sequel
83
83
  super
84
84
  end
85
85
  end
86
+
87
+ # HSQLDB uses clob for text types.
88
+ def uses_clob_for_text?
89
+ true
90
+ end
86
91
  end
87
92
 
88
93
  # Dataset class for HSQLDB datasets accessed via JDBC.
@@ -42,12 +42,17 @@ module Sequel
42
42
  end
43
43
  end
44
44
 
45
- # MySQL 5.1.12 JDBC adapter requires this to be true,
45
+ # MySQL 5.1.12 JDBC adapter requires generated keys
46
46
  # and previous versions don't mind.
47
- def requires_return_generated_keys?
48
- true
47
+ def execute_statement_insert(stmt, sql)
48
+ stmt.executeUpdate(sql, JavaSQL::Statement.RETURN_GENERATED_KEYS)
49
49
  end
50
-
50
+
51
+ # Return generated keys for insert statements.
52
+ def prepare_jdbc_statement(conn, sql, opts)
53
+ opts[:type] == :insert ? conn.prepareStatement(sql, JavaSQL::Statement.RETURN_GENERATED_KEYS) : super
54
+ end
55
+
51
56
  # Convert tinyint(1) type to boolean
52
57
  def schema_column_type(db_type)
53
58
  db_type == 'tinyint(1)' ? :boolean : super
@@ -41,9 +41,6 @@ module Sequel
41
41
  include Sequel::MySQL::DatabaseMethods
42
42
  include Sequel::MySQL::PreparedStatements::DatabaseMethods
43
43
 
44
- # Mysql::Error messages that indicate the current connection should be disconnected
45
- MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away|Lost connection to MySQL server during query)/
46
-
47
44
  # Regular expression used for getting accurate number of rows
48
45
  # matched by an update statement.
49
46
  AFFECTED_ROWS_RE = /Rows matched:\s+(\d+)\s+Changed:\s+\d+\s+Warnings:\s+\d+/.freeze
@@ -9,9 +9,6 @@ module Sequel
9
9
  include Sequel::MySQL::DatabaseMethods
10
10
  include Sequel::MySQL::PreparedStatements::DatabaseMethods
11
11
 
12
- # Mysql::Error messages that indicate the current connection should be disconnected
13
- MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away|This connection is still waiting for a result, try again once you have the result|closed MySQL connection)/
14
-
15
12
  set_adapter_scheme :mysql2
16
13
 
17
14
  # Whether to convert tinyint columns to bool for this database
@@ -13,7 +13,10 @@ module Sequel
13
13
  # ORA-03114: not connected to ORACLE
14
14
  CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]
15
15
 
16
- ORACLE_TYPES = {:blob=>lambda{|b| Sequel::SQL::Blob.new(b.read)}}
16
+ ORACLE_TYPES = {
17
+ :blob=>lambda{|b| Sequel::SQL::Blob.new(b.read)},
18
+ :clob=>lambda{|b| b.read}
19
+ }
17
20
 
18
21
  # Hash of conversion procs for this database.
19
22
  attr_reader :conversion_procs
@@ -95,7 +95,7 @@ module Sequel
95
95
  # PGconn subclass for connection specific methods used with the
96
96
  # pg, postgres, or postgres-pr driver.
97
97
  class Adapter < ::PGconn
98
- DISCONNECT_ERROR_RE = /\Acould not receive data from server: Software caused connection abort/
98
+ DISCONNECT_ERROR_RE = /\Acould not receive data from server/
99
99
 
100
100
  self.translate_results = false if respond_to?(:translate_results=)
101
101
 
@@ -109,7 +109,7 @@ module Sequel
109
109
  def check_disconnect_errors
110
110
  begin
111
111
  yield
112
- rescue PGError =>e
112
+ rescue PGError => e
113
113
  disconnect = false
114
114
  begin
115
115
  s = status
@@ -121,7 +121,7 @@ module Sequel
121
121
  disconnect ||= e.message =~ DISCONNECT_ERROR_RE
122
122
  disconnect ? raise(Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError)) : raise
123
123
  ensure
124
- block if status_ok
124
+ block if status_ok && !disconnect
125
125
  end
126
126
  end
127
127
 
@@ -382,7 +382,7 @@ module Sequel
382
382
  synchronize(opts[:server]) do |conn|
383
383
  begin
384
384
  channels = Array(channels)
385
- channels.each{|channel| conn.execute("LISTEN #{channel}")}
385
+ channels.each{|channel| conn.execute("LISTEN #{dataset.send(:table_ref, channel)}")}
386
386
  opts[:after_listen].call(conn) if opts[:after_listen]
387
387
  timeout = opts[:timeout] ? [opts[:timeout]] : []
388
388
  if l = opts[:loop]
@@ -7,8 +7,15 @@ module Sequel
7
7
  end
8
8
 
9
9
  # Doesn't work, due to security restrictions on MSysObjects
10
- def tables
11
- from(:MSysObjects).filter(:Type=>1, :Flags=>0).select_map(:Name).map{|x| x.to_sym}
10
+ #def tables
11
+ # from(:MSysObjects).filter(:Type=>1, :Flags=>0).select_map(:Name).map{|x| x.to_sym}
12
+ #end
13
+
14
+ # Access doesn't support renaming tables from an SQL query,
15
+ # so create a copy of the table and then drop the from table.
16
+ def rename_table(from_table, to_table)
17
+ create_table(to_table, :as=>from(from_table))
18
+ drop_table(from_table)
12
19
  end
13
20
 
14
21
  # Access uses type Counter for an autoincrementing keys
@@ -18,6 +25,29 @@ module Sequel
18
25
 
19
26
  private
20
27
 
28
+ def alter_table_op_sql(table, op)
29
+ case op[:op]
30
+ when :set_column_type
31
+ "ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(op)}"
32
+ else
33
+ super
34
+ end
35
+ end
36
+
37
+ # Access doesn't support CREATE TABLE AS, it only supports SELECT INTO.
38
+ # Emulating CREATE TABLE AS using SELECT INTO is only possible if a dataset
39
+ # is given as the argument, it can't work with a string, so raise an
40
+ # Error if a string is given.
41
+ def create_table_as(name, ds, options)
42
+ raise(Error, "must provide dataset instance as value of create_table :as option on Access") unless ds.is_a?(Sequel::Dataset)
43
+ run(ds.into(name).sql)
44
+ end
45
+
46
+ # The SQL to drop an index for the table.
47
+ def drop_index_sql(table, op)
48
+ "DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))} ON #{quote_schema_table(table)}"
49
+ end
50
+
21
51
  def identifier_input_method_default
22
52
  nil
23
53
  end
@@ -25,21 +55,155 @@ module Sequel
25
55
  def identifier_output_method_default
26
56
  nil
27
57
  end
58
+
59
+ # Access doesn't have a 64-bit integer type, so use integer and hope
60
+ # the user isn't using more than 32 bits.
61
+ def type_literal_generic_bignum(column)
62
+ :integer
63
+ end
64
+
65
+ # Access doesn't have a true boolean class, so it uses bit
66
+ def type_literal_generic_trueclass(column)
67
+ :bit
68
+ end
69
+
70
+ # Access uses image type for blobs
71
+ def type_literal_generic_file(column)
72
+ :image
73
+ end
28
74
  end
29
75
 
30
76
  module DatasetMethods
31
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select limit distinct columns from join where group order having compounds')
77
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct limit columns into from join where group order having compounds')
32
78
  DATE_FORMAT = '#%Y-%m-%d#'.freeze
33
79
  TIMESTAMP_FORMAT = '#%Y-%m-%d %H:%M:%S#'.freeze
34
80
  TOP = " TOP ".freeze
35
81
  BRACKET_CLOSE = Dataset::BRACKET_CLOSE
36
82
  BRACKET_OPEN = Dataset::BRACKET_OPEN
83
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
84
+ PAREN_OPEN = Dataset::PAREN_OPEN
85
+ INTO = Dataset::INTO
86
+ FROM = Dataset::FROM
87
+ NOT_EQUAL = ' <> '.freeze
88
+ OPS = {:'%'=>' Mod '.freeze, :'||'=>' & '.freeze}
89
+ BOOL_FALSE = '0'.freeze
90
+ BOOL_TRUE = '-1'.freeze
91
+ DATE_FUNCTION = 'Date()'.freeze
92
+ NOW_FUNCTION = 'Now()'.freeze
93
+ TIME_FUNCTION = 'Time()'.freeze
94
+ CAST_TYPES = {String=>:CStr, Integer=>:CLng, Date=>:CDate, Time=>:CDate, DateTime=>:CDate, Numeric=>:CDec, BigDecimal=>:CDec, File=>:CStr, Float=>:CDbl, TrueClass=>:CBool, FalseClass=>:CBool}
95
+
96
+ EXTRACT_MAP = {:year=>"'yyyy'", :month=>"'m'", :day=>"'d'", :hour=>"'h'", :minute=>"'n'", :second=>"'s'"}
97
+ COMMA = Dataset::COMMA
98
+ DATEPART_OPEN = "datepart(".freeze
99
+
100
+ # Access doesn't support CASE, but it can be emulated with nested
101
+ # IIF function calls.
102
+ def case_expression_sql_append(sql, ce)
103
+ literal_append(sql, ce.with_merged_expression.conditions.reverse.inject(ce.default){|exp,(cond,val)| Sequel::SQL::Function.new(:IIF, cond, val, exp)})
104
+ end
105
+
106
+ # Access doesn't support CAST, it uses separate functions for
107
+ # type conversion
108
+ def cast_sql_append(sql, expr, type)
109
+ sql << CAST_TYPES.fetch(type, type).to_s
110
+ sql << PAREN_OPEN
111
+ literal_append(sql, expr)
112
+ sql << PAREN_CLOSE
113
+ end
114
+
115
+ def complex_expression_sql_append(sql, op, args)
116
+ case op
117
+ when :ILIKE
118
+ super(sql, :LIKE, args)
119
+ when :'NOT ILIKE'
120
+ super(sql, :'NOT LIKE', args)
121
+ when :'!='
122
+ sql << PAREN_OPEN
123
+ literal_append(sql, args.at(0))
124
+ sql << NOT_EQUAL
125
+ literal_append(sql, args.at(1))
126
+ sql << PAREN_CLOSE
127
+ when :'%', :'||'
128
+ sql << PAREN_OPEN
129
+ c = false
130
+ op_str = OPS[op]
131
+ args.each do |a|
132
+ sql << op_str if c
133
+ literal_append(sql, a)
134
+ c ||= true
135
+ end
136
+ sql << PAREN_CLOSE
137
+ when :extract
138
+ part = args.at(0)
139
+ raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
140
+ sql << DATEPART_OPEN << format.to_s << COMMA
141
+ literal_append(sql, args.at(1))
142
+ sql << PAREN_CLOSE
143
+ else
144
+ super
145
+ end
146
+ end
147
+
148
+ # Use Date() and Now() for CURRENT_DATE and CURRENT_TIMESTAMP
149
+ def constant_sql_append(sql, constant)
150
+ case constant
151
+ when :CURRENT_DATE
152
+ sql << DATE_FUNCTION
153
+ when :CURRENT_TIMESTAMP
154
+ sql << NOW_FUNCTION
155
+ when :CURRENT_TIME
156
+ sql << TIME_FUNCTION
157
+ else
158
+ super
159
+ end
160
+ end
161
+
162
+ # Emulate cross join by using multiple tables in the FROM clause.
163
+ def cross_join(table)
164
+ clone(:from=>@opts[:from] + [table])
165
+ end
166
+
167
+ def emulated_function_sql_append(sql, f)
168
+ case f.f
169
+ when :char_length
170
+ literal_append(sql, SQL::Function.new(:len, f.args.first))
171
+ else
172
+ super
173
+ end
174
+ end
175
+
176
+ # Specify a table for a SELECT ... INTO query.
177
+ def into(table)
178
+ clone(:into => table)
179
+ end
37
180
 
38
181
  # Access doesn't support INTERSECT or EXCEPT
39
182
  def supports_intersect_except?
40
183
  false
41
184
  end
42
185
 
186
+ # Access does not support IS TRUE
187
+ def supports_is_true?
188
+ false
189
+ end
190
+
191
+ # Access doesn't support JOIN USING
192
+ def supports_join_using?
193
+ false
194
+ end
195
+
196
+ # Access does not support multiple columns for the IN/NOT IN operators
197
+ def supports_multiple_column_in?
198
+ false
199
+ end
200
+
201
+ # Access doesn't support truncate, so do a delete instead.
202
+ def truncate
203
+ delete
204
+ nil
205
+ end
206
+
43
207
  private
44
208
 
45
209
  # Access uses # to quote dates
@@ -53,6 +217,44 @@ module Sequel
53
217
  end
54
218
  alias literal_time literal_datetime
55
219
 
220
+ # Use 0 for false on MSSQL
221
+ def literal_false
222
+ BOOL_FALSE
223
+ end
224
+
225
+ # Use 0 for false on MSSQL
226
+ def literal_true
227
+ BOOL_TRUE
228
+ end
229
+
230
+ # Access requires parentheses when joining more than one table
231
+ def select_from_sql(sql)
232
+ if f = @opts[:from]
233
+ sql << FROM
234
+ if (j = @opts[:join]) && !j.empty?
235
+ sql << (PAREN_OPEN * j.length)
236
+ end
237
+ source_list_append(sql, f)
238
+ end
239
+ end
240
+
241
+ def select_into_sql(sql)
242
+ if i = @opts[:into]
243
+ sql << INTO
244
+ identifier_append(sql, i)
245
+ end
246
+ end
247
+
248
+ # Access requires parentheses when joining more than one table
249
+ def select_join_sql(sql)
250
+ if js = @opts[:join]
251
+ js.each do |j|
252
+ literal_append(sql, j)
253
+ sql << PAREN_CLOSE
254
+ end
255
+ end
256
+ end
257
+
56
258
  # Access uses TOP for limits
57
259
  def select_limit_sql(sql)
58
260
  if l = @opts[:limit]