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.
- data/CHANGELOG +30 -0
- data/README.rdoc +4 -3
- data/doc/active_record.rdoc +1 -1
- data/doc/opening_databases.rdoc +7 -0
- data/doc/release_notes/3.40.0.txt +73 -0
- data/lib/sequel/adapters/ado.rb +29 -3
- data/lib/sequel/adapters/ado/access.rb +334 -0
- data/lib/sequel/adapters/ado/mssql.rb +0 -6
- data/lib/sequel/adapters/cubrid.rb +143 -0
- data/lib/sequel/adapters/jdbc.rb +26 -18
- data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
- data/lib/sequel/adapters/jdbc/derby.rb +7 -7
- data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -4
- data/lib/sequel/adapters/mysql.rb +0 -3
- data/lib/sequel/adapters/mysql2.rb +0 -3
- data/lib/sequel/adapters/oracle.rb +4 -1
- data/lib/sequel/adapters/postgres.rb +4 -4
- data/lib/sequel/adapters/shared/access.rb +205 -3
- data/lib/sequel/adapters/shared/cubrid.rb +216 -0
- data/lib/sequel/adapters/shared/db2.rb +7 -2
- data/lib/sequel/adapters/shared/mssql.rb +3 -34
- data/lib/sequel/adapters/shared/mysql.rb +4 -33
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +11 -0
- data/lib/sequel/adapters/shared/oracle.rb +5 -0
- data/lib/sequel/adapters/shared/postgres.rb +2 -1
- data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/query.rb +30 -7
- data/lib/sequel/database/schema_methods.rb +7 -2
- data/lib/sequel/dataset/query.rb +9 -10
- data/lib/sequel/dataset/sql.rb +14 -26
- data/lib/sequel/extensions/pg_hstore.rb +19 -0
- data/lib/sequel/extensions/pg_row.rb +5 -5
- data/lib/sequel/plugins/association_pks.rb +121 -18
- data/lib/sequel/plugins/json_serializer.rb +19 -0
- data/lib/sequel/sql.rb +11 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +42 -0
- data/spec/core/database_spec.rb +17 -0
- data/spec/core/dataset_spec.rb +11 -0
- data/spec/core/expression_filters_spec.rb +13 -0
- data/spec/extensions/association_pks_spec.rb +163 -3
- data/spec/extensions/pg_hstore_spec.rb +6 -0
- data/spec/extensions/pg_row_spec.rb +17 -0
- data/spec/integration/associations_test.rb +1 -1
- data/spec/integration/dataset_test.rb +13 -13
- data/spec/integration/plugin_test.rb +232 -7
- data/spec/integration/schema_test.rb +8 -12
- data/spec/integration/spec_helper.rb +1 -1
- data/spec/integration/type_test.rb +6 -0
- 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
|
data/lib/sequel/adapters/jdbc.rb
CHANGED
@@ -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)
|
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
|
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
|
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
|
182
|
+
# so remove
|
178
183
|
# conditions.
|
179
184
|
def case_expression_sql_append(sql, ce)
|
180
|
-
|
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
|
@@ -42,12 +42,17 @@ module Sequel
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
# MySQL 5.1.12 JDBC adapter requires
|
45
|
+
# MySQL 5.1.12 JDBC adapter requires generated keys
|
46
46
|
# and previous versions don't mind.
|
47
|
-
def
|
48
|
-
|
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 = {
|
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
|
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
|
-
|
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
|
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]
|