colincasey-sequel 2.10.0 → 2.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. data/CHANGELOG +7 -1
  2. data/doc/advanced_associations.rdoc +614 -0
  3. data/doc/cheat_sheet.rdoc +223 -0
  4. data/doc/dataset_filtering.rdoc +158 -0
  5. data/doc/prepared_statements.rdoc +104 -0
  6. data/doc/release_notes/1.0.txt +38 -0
  7. data/doc/release_notes/1.1.txt +143 -0
  8. data/doc/release_notes/1.3.txt +101 -0
  9. data/doc/release_notes/1.4.0.txt +53 -0
  10. data/doc/release_notes/1.5.0.txt +155 -0
  11. data/doc/release_notes/2.0.0.txt +298 -0
  12. data/doc/release_notes/2.1.0.txt +271 -0
  13. data/doc/release_notes/2.10.0.txt +328 -0
  14. data/doc/release_notes/2.2.0.txt +253 -0
  15. data/doc/release_notes/2.3.0.txt +88 -0
  16. data/doc/release_notes/2.4.0.txt +106 -0
  17. data/doc/release_notes/2.5.0.txt +137 -0
  18. data/doc/release_notes/2.6.0.txt +157 -0
  19. data/doc/release_notes/2.7.0.txt +166 -0
  20. data/doc/release_notes/2.8.0.txt +171 -0
  21. data/doc/release_notes/2.9.0.txt +97 -0
  22. data/doc/schema.rdoc +29 -0
  23. data/doc/sharding.rdoc +113 -0
  24. data/lib/sequel.rb +1 -0
  25. data/lib/sequel_core/adapters/ado.rb +89 -0
  26. data/lib/sequel_core/adapters/db2.rb +143 -0
  27. data/lib/sequel_core/adapters/dbi.rb +112 -0
  28. data/lib/sequel_core/adapters/do/mysql.rb +38 -0
  29. data/lib/sequel_core/adapters/do/postgres.rb +92 -0
  30. data/lib/sequel_core/adapters/do/sqlite.rb +31 -0
  31. data/lib/sequel_core/adapters/do.rb +205 -0
  32. data/lib/sequel_core/adapters/firebird.rb +298 -0
  33. data/lib/sequel_core/adapters/informix.rb +85 -0
  34. data/lib/sequel_core/adapters/jdbc/h2.rb +69 -0
  35. data/lib/sequel_core/adapters/jdbc/mysql.rb +66 -0
  36. data/lib/sequel_core/adapters/jdbc/oracle.rb +23 -0
  37. data/lib/sequel_core/adapters/jdbc/postgresql.rb +113 -0
  38. data/lib/sequel_core/adapters/jdbc/sqlite.rb +43 -0
  39. data/lib/sequel_core/adapters/jdbc.rb +491 -0
  40. data/lib/sequel_core/adapters/mysql.rb +369 -0
  41. data/lib/sequel_core/adapters/odbc.rb +174 -0
  42. data/lib/sequel_core/adapters/openbase.rb +68 -0
  43. data/lib/sequel_core/adapters/oracle.rb +107 -0
  44. data/lib/sequel_core/adapters/postgres.rb +456 -0
  45. data/lib/sequel_core/adapters/shared/ms_access.rb +110 -0
  46. data/lib/sequel_core/adapters/shared/mssql.rb +102 -0
  47. data/lib/sequel_core/adapters/shared/mysql.rb +325 -0
  48. data/lib/sequel_core/adapters/shared/oracle.rb +61 -0
  49. data/lib/sequel_core/adapters/shared/postgres.rb +715 -0
  50. data/lib/sequel_core/adapters/shared/progress.rb +31 -0
  51. data/lib/sequel_core/adapters/shared/sqlite.rb +265 -0
  52. data/lib/sequel_core/adapters/sqlite.rb +248 -0
  53. data/lib/sequel_core/connection_pool.rb +258 -0
  54. data/lib/sequel_core/core_ext.rb +217 -0
  55. data/lib/sequel_core/core_sql.rb +202 -0
  56. data/lib/sequel_core/database/schema.rb +164 -0
  57. data/lib/sequel_core/database.rb +691 -0
  58. data/lib/sequel_core/dataset/callback.rb +13 -0
  59. data/lib/sequel_core/dataset/convenience.rb +237 -0
  60. data/lib/sequel_core/dataset/pagination.rb +96 -0
  61. data/lib/sequel_core/dataset/prepared_statements.rb +220 -0
  62. data/lib/sequel_core/dataset/query.rb +41 -0
  63. data/lib/sequel_core/dataset/schema.rb +15 -0
  64. data/lib/sequel_core/dataset/sql.rb +1010 -0
  65. data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
  66. data/lib/sequel_core/dataset/unsupported.rb +43 -0
  67. data/lib/sequel_core/dataset.rb +511 -0
  68. data/lib/sequel_core/deprecated.rb +26 -0
  69. data/lib/sequel_core/exceptions.rb +44 -0
  70. data/lib/sequel_core/migration.rb +212 -0
  71. data/lib/sequel_core/object_graph.rb +230 -0
  72. data/lib/sequel_core/pretty_table.rb +71 -0
  73. data/lib/sequel_core/schema/generator.rb +320 -0
  74. data/lib/sequel_core/schema/sql.rb +325 -0
  75. data/lib/sequel_core/schema.rb +2 -0
  76. data/lib/sequel_core/sql.rb +887 -0
  77. data/lib/sequel_core/version.rb +11 -0
  78. data/lib/sequel_core.rb +172 -0
  79. data/lib/sequel_model/association_reflection.rb +267 -0
  80. data/lib/sequel_model/associations.rb +499 -0
  81. data/lib/sequel_model/base.rb +523 -0
  82. data/lib/sequel_model/caching.rb +82 -0
  83. data/lib/sequel_model/dataset_methods.rb +26 -0
  84. data/lib/sequel_model/eager_loading.rb +370 -0
  85. data/lib/sequel_model/exceptions.rb +7 -0
  86. data/lib/sequel_model/hooks.rb +101 -0
  87. data/lib/sequel_model/inflector.rb +281 -0
  88. data/lib/sequel_model/plugins.rb +62 -0
  89. data/lib/sequel_model/record.rb +568 -0
  90. data/lib/sequel_model/schema.rb +49 -0
  91. data/lib/sequel_model/validations.rb +429 -0
  92. data/lib/sequel_model.rb +91 -0
  93. data/spec/adapters/ado_spec.rb +46 -0
  94. data/spec/adapters/firebird_spec.rb +376 -0
  95. data/spec/adapters/informix_spec.rb +96 -0
  96. data/spec/adapters/mysql_spec.rb +881 -0
  97. data/spec/adapters/oracle_spec.rb +244 -0
  98. data/spec/adapters/postgres_spec.rb +687 -0
  99. data/spec/adapters/spec_helper.rb +10 -0
  100. data/spec/adapters/sqlite_spec.rb +555 -0
  101. data/spec/integration/dataset_test.rb +134 -0
  102. data/spec/integration/eager_loader_test.rb +696 -0
  103. data/spec/integration/prepared_statement_test.rb +130 -0
  104. data/spec/integration/schema_test.rb +180 -0
  105. data/spec/integration/spec_helper.rb +58 -0
  106. data/spec/integration/type_test.rb +96 -0
  107. data/spec/rcov.opts +6 -0
  108. data/spec/sequel_core/connection_pool_spec.rb +526 -0
  109. data/spec/sequel_core/core_ext_spec.rb +156 -0
  110. data/spec/sequel_core/core_sql_spec.rb +522 -0
  111. data/spec/sequel_core/database_spec.rb +1188 -0
  112. data/spec/sequel_core/dataset_spec.rb +3481 -0
  113. data/spec/sequel_core/expression_filters_spec.rb +363 -0
  114. data/spec/sequel_core/migration_spec.rb +261 -0
  115. data/spec/sequel_core/object_graph_spec.rb +272 -0
  116. data/spec/sequel_core/pretty_table_spec.rb +58 -0
  117. data/spec/sequel_core/schema_generator_spec.rb +167 -0
  118. data/spec/sequel_core/schema_spec.rb +780 -0
  119. data/spec/sequel_core/spec_helper.rb +55 -0
  120. data/spec/sequel_core/version_spec.rb +7 -0
  121. data/spec/sequel_model/association_reflection_spec.rb +93 -0
  122. data/spec/sequel_model/associations_spec.rb +1767 -0
  123. data/spec/sequel_model/base_spec.rb +419 -0
  124. data/spec/sequel_model/caching_spec.rb +215 -0
  125. data/spec/sequel_model/dataset_methods_spec.rb +78 -0
  126. data/spec/sequel_model/eager_loading_spec.rb +1165 -0
  127. data/spec/sequel_model/hooks_spec.rb +485 -0
  128. data/spec/sequel_model/inflector_spec.rb +119 -0
  129. data/spec/sequel_model/model_spec.rb +588 -0
  130. data/spec/sequel_model/plugins_spec.rb +80 -0
  131. data/spec/sequel_model/record_spec.rb +1184 -0
  132. data/spec/sequel_model/schema_spec.rb +90 -0
  133. data/spec/sequel_model/spec_helper.rb +78 -0
  134. data/spec/sequel_model/validations_spec.rb +1067 -0
  135. data/spec/spec.opts +0 -0
  136. data/spec/spec_config.rb.example +10 -0
  137. metadata +177 -3
@@ -0,0 +1,369 @@
1
+ require 'mysql'
2
+ require 'sequel_core/adapters/shared/mysql'
3
+ require 'sequel_core/dataset/stored_procedures'
4
+
5
+ module Sequel
6
+ # Module for holding all MySQL-related classes and modules for Sequel.
7
+ module MySQL
8
+ # Mapping of type numbers to conversion methods.
9
+ MYSQL_TYPES = {
10
+ 0 => :to_d, # MYSQL_TYPE_DECIMAL
11
+ 1 => :to_i, # MYSQL_TYPE_TINY
12
+ 2 => :to_i, # MYSQL_TYPE_SHORT
13
+ 3 => :to_i, # MYSQL_TYPE_LONG
14
+ 4 => :to_f, # MYSQL_TYPE_FLOAT
15
+ 5 => :to_f, # MYSQL_TYPE_DOUBLE
16
+ # 6 => ??, # MYSQL_TYPE_NULL
17
+ 7 => :to_sequel_time, # MYSQL_TYPE_TIMESTAMP
18
+ 8 => :to_i, # MYSQL_TYPE_LONGLONG
19
+ 9 => :to_i, # MYSQL_TYPE_INT24
20
+ 10 => :to_date, # MYSQL_TYPE_DATE
21
+ 11 => :to_time, # MYSQL_TYPE_TIME
22
+ 12 => :to_sequel_time, # MYSQL_TYPE_DATETIME
23
+ 13 => :to_i, # MYSQL_TYPE_YEAR
24
+ 14 => :to_date, # MYSQL_TYPE_NEWDATE
25
+ # 15 => :to_s # MYSQL_TYPE_VARCHAR
26
+ # 16 => :to_s, # MYSQL_TYPE_BIT
27
+ 246 => :to_d, # MYSQL_TYPE_NEWDECIMAL
28
+ 247 => :to_i, # MYSQL_TYPE_ENUM
29
+ 248 => :to_i, # MYSQL_TYPE_SET
30
+ 249 => :to_sequel_blob, # MYSQL_TYPE_TINY_BLOB
31
+ 250 => :to_sequel_blob, # MYSQL_TYPE_MEDIUM_BLOB
32
+ 251 => :to_sequel_blob, # MYSQL_TYPE_LONG_BLOB
33
+ 252 => :to_sequel_blob, # MYSQL_TYPE_BLOB
34
+ # 253 => :to_s, # MYSQL_TYPE_VAR_STRING
35
+ # 254 => :to_s, # MYSQL_TYPE_STRING
36
+ # 255 => :to_s # MYSQL_TYPE_GEOMETRY
37
+ }
38
+
39
+ # Database class for MySQL databases used with Sequel.
40
+ class Database < Sequel::Database
41
+ include Sequel::MySQL::DatabaseMethods
42
+
43
+ set_adapter_scheme :mysql
44
+
45
+ # Support stored procedures on MySQL
46
+ def call_sproc(name, opts={}, &block)
47
+ args = opts[:args] || []
48
+ execute("CALL #{name}#{args.empty? ? '()' : literal(args)}", opts.merge(:sproc=>false), &block)
49
+ end
50
+
51
+ # Connect to the database. In addition to the usual database options,
52
+ # the following options have effect:
53
+ #
54
+ # * :auto_is_null - Set to true to use MySQL default behavior of having
55
+ # a filter for an autoincrement column equals NULL to return the last
56
+ # inserted row.
57
+ # * :charset - Same as :encoding (:encoding takes precendence)
58
+ # * :compress - Set to false to not compress results from the server
59
+ # * :encoding - Set all the related character sets for this
60
+ # connection (connection, client, database, server, and results).
61
+ # * :socket - Use a unix socket file instead of connecting via TCP/IP.
62
+ # * :timeout - Set the timeout in seconds before the server will
63
+ # disconnect this connection.
64
+ def connect(server)
65
+ opts = server_opts(server)
66
+ conn = Mysql.init
67
+ conn.options(Mysql::OPT_LOCAL_INFILE, "client")
68
+ if encoding = opts[:encoding] || opts[:charset]
69
+ # set charset _before_ the connect. using an option instead of "SET (NAMES|CHARACTER_SET_*)" works across reconnects
70
+ conn.options(Mysql::SET_CHARSET_NAME, encoding)
71
+ end
72
+ conn.real_connect(
73
+ opts[:host] || 'localhost',
74
+ opts[:user],
75
+ opts[:password],
76
+ opts[:database],
77
+ opts[:port],
78
+ opts[:socket],
79
+ Mysql::CLIENT_MULTI_RESULTS +
80
+ Mysql::CLIENT_MULTI_STATEMENTS +
81
+ (opts[:compress] == false ? 0 : Mysql::CLIENT_COMPRESS)
82
+ )
83
+
84
+ # increase timeout so mysql server doesn't disconnect us
85
+ conn.query("set @@wait_timeout = #{opts[:timeout] || 2592000}")
86
+
87
+ # By default, MySQL 'where id is null' selects the last inserted id
88
+ conn.query("set SQL_AUTO_IS_NULL=0") unless opts[:auto_is_null]
89
+
90
+ conn.query_with_result = false
91
+ conn.meta_eval{attr_accessor :prepared_statements}
92
+ conn.prepared_statements = {}
93
+ conn.reconnect = true
94
+ conn
95
+ end
96
+
97
+ # Returns instance of Sequel::MySQL::Dataset with the given options.
98
+ def dataset(opts = nil)
99
+ MySQL::Dataset.new(self, opts)
100
+ end
101
+
102
+ # Executes the given SQL using an available connection, yielding the
103
+ # connection if the block is given.
104
+ def execute(sql, opts={}, &block)
105
+ return call_sproc(sql, opts, &block) if opts[:sproc]
106
+ return execute_prepared_statement(sql, opts, &block) if Symbol === sql
107
+ begin
108
+ synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
109
+ rescue Mysql::Error => e
110
+ raise_error(e)
111
+ end
112
+ end
113
+
114
+ # Return the version of the MySQL server two which we are connecting.
115
+ def server_version(server=nil)
116
+ @server_version ||= (synchronize(server){|conn| conn.server_version if conn.respond_to?(:server_version)} || super)
117
+ end
118
+
119
+ # Support single level transactions on MySQL.
120
+ def transaction(server=nil)
121
+ synchronize(server) do |conn|
122
+ return yield(conn) if @transactions.include?(Thread.current)
123
+ log_info(begin_transaction_sql)
124
+ conn.query(begin_transaction_sql)
125
+ begin
126
+ @transactions << Thread.current
127
+ yield(conn)
128
+ rescue ::Exception => e
129
+ log_info(rollback_transaction_sql)
130
+ conn.query(rollback_transaction_sql)
131
+ transaction_error(e, Mysql::Error)
132
+ ensure
133
+ unless e
134
+ log_info(commit_transaction_sql)
135
+ conn.query(commit_transaction_sql)
136
+ end
137
+ @transactions.delete(Thread.current)
138
+ end
139
+ end
140
+ end
141
+
142
+ private
143
+
144
+ # Execute the given SQL on the given connection. If the :type
145
+ # option is :select, yield the result of the query, otherwise
146
+ # yield the connection if a block is given.
147
+ def _execute(conn, sql, opts)
148
+ log_info(sql)
149
+ conn.query(sql)
150
+ if opts[:type] == :select
151
+ loop do
152
+ begin
153
+ r = conn.use_result
154
+ rescue Mysql::Error
155
+ nil
156
+ else
157
+ begin
158
+ yield r
159
+ ensure
160
+ r.free
161
+ end
162
+ end
163
+ break unless conn.respond_to?(:next_result) && conn.next_result
164
+ end
165
+ else
166
+ yield conn if block_given?
167
+ end
168
+ end
169
+
170
+ # MySQL doesn't need the connection pool to convert exceptions.
171
+ def connection_pool_default_options
172
+ super.merge(:pool_convert_exceptions=>false)
173
+ end
174
+
175
+ # The database name when using the native adapter is always stored in
176
+ # the :database option.
177
+ def database_name
178
+ @opts[:database]
179
+ end
180
+
181
+ # Closes given database connection.
182
+ def disconnect_connection(c)
183
+ c.close
184
+ end
185
+
186
+ # Executes a prepared statement on an available connection. If the
187
+ # prepared statement already exists for the connection and has the same
188
+ # SQL, reuse it, otherwise, prepare the new statement. Because of the
189
+ # usual MySQL stupidity, we are forced to name arguments via separate
190
+ # SET queries. Use @sequel_arg_N (for N starting at 1) for these
191
+ # arguments.
192
+ def execute_prepared_statement(ps_name, opts, &block)
193
+ args = opts[:arguments]
194
+ ps = prepared_statements[ps_name]
195
+ sql = ps.prepared_sql
196
+ synchronize(opts[:server]) do |conn|
197
+ unless conn.prepared_statements[ps_name] == sql
198
+ conn.prepared_statements[ps_name] = sql
199
+ s = "PREPARE #{ps_name} FROM '#{::Mysql.quote(sql)}'"
200
+ log_info(s)
201
+ conn.query(s)
202
+ end
203
+ i = 0
204
+ args.each do |arg|
205
+ s = "SET @sequel_arg_#{i+=1} = #{literal(arg)}"
206
+ log_info(s)
207
+ conn.query(s)
208
+ end
209
+ _execute(conn, "EXECUTE #{ps_name}#{" USING #{(1..i).map{|j| "@sequel_arg_#{j}"}.join(', ')}" unless i == 0}", opts, &block)
210
+ end
211
+ end
212
+ end
213
+
214
+ # Dataset class for MySQL datasets accessed via the native driver.
215
+ class Dataset < Sequel::Dataset
216
+ include Sequel::MySQL::DatasetMethods
217
+ include StoredProcedures
218
+
219
+ # Methods to add to MySQL prepared statement calls without using a
220
+ # real database prepared statement and bound variables.
221
+ module CallableStatementMethods
222
+ # Extend given dataset with this module so subselects inside subselects in
223
+ # prepared statements work.
224
+ def subselect_sql(ds)
225
+ ps = ds.to_prepared_statement(:select)
226
+ ps.extend(CallableStatementMethods)
227
+ ps.prepared_args = prepared_args
228
+ ps.prepared_sql
229
+ end
230
+ end
231
+
232
+ # Methods for MySQL prepared statements using the native driver.
233
+ module PreparedStatementMethods
234
+ include Sequel::Dataset::UnnumberedArgumentMapper
235
+
236
+ private
237
+
238
+ # Execute the prepared statement with the bind arguments instead of
239
+ # the given SQL.
240
+ def execute(sql, opts={}, &block)
241
+ super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
242
+ end
243
+
244
+ # Same as execute, explicit due to intricacies of alias and super.
245
+ def execute_dui(sql, opts={}, &block)
246
+ super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
247
+ end
248
+ end
249
+
250
+ # Methods for MySQL stored procedures using the native driver.
251
+ module StoredProcedureMethods
252
+ include Sequel::Dataset::StoredProcedureMethods
253
+
254
+ private
255
+
256
+ # Execute the database stored procedure with the stored arguments.
257
+ def execute(sql, opts={}, &block)
258
+ super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
259
+ end
260
+
261
+ # Same as execute, explicit due to intricacies of alias and super.
262
+ def execute_dui(sql, opts={}, &block)
263
+ super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
264
+ end
265
+ end
266
+
267
+ # MySQL is different in that it supports prepared statements but not bound
268
+ # variables outside of prepared statements. The default implementation
269
+ # breaks the use of subselects in prepared statements, so extend the
270
+ # temporary prepared statement that this creates with a module that
271
+ # fixes it.
272
+ def call(type, bind_arguments={}, values=nil)
273
+ ps = to_prepared_statement(type, values)
274
+ ps.extend(CallableStatementMethods)
275
+ ps.call(bind_arguments)
276
+ end
277
+
278
+ # Delete rows matching this dataset
279
+ def delete(opts = nil)
280
+ execute_dui(delete_sql(opts)){|c| c.affected_rows}
281
+ end
282
+
283
+ # Yield all rows matching this dataset
284
+ def fetch_rows(sql)
285
+ execute(sql) do |r|
286
+ column_types = []
287
+ @columns = r.fetch_fields.map{|f| column_types << f.type; output_identifier(f.name)}
288
+ while row = r.fetch_row
289
+ h = {}
290
+ @columns.each_with_index {|f, i| h[f] = convert_type(row[i], column_types[i])}
291
+ yield h
292
+ end
293
+ end
294
+ self
295
+ end
296
+
297
+ # Insert a new value into this dataset
298
+ def insert(*values)
299
+ execute_dui(insert_sql(*values)){|c| c.insert_id}
300
+ end
301
+
302
+ # Handle correct quoting of strings using ::MySQL.quote.
303
+ def literal(v)
304
+ case v
305
+ when LiteralString
306
+ v
307
+ when String
308
+ "'#{::Mysql.quote(v)}'"
309
+ else
310
+ super
311
+ end
312
+ end
313
+
314
+ # Store the given type of prepared statement in the associated database
315
+ # with the given name.
316
+ def prepare(type, name=nil, values=nil)
317
+ ps = to_prepared_statement(type, values)
318
+ ps.extend(PreparedStatementMethods)
319
+ if name
320
+ ps.prepared_statement_name = name
321
+ db.prepared_statements[name] = ps
322
+ end
323
+ ps
324
+ end
325
+
326
+ # Replace (update or insert) the matching row.
327
+ def replace(*args)
328
+ execute_dui(replace_sql(*args)){|c| c.insert_id}
329
+ end
330
+
331
+ # Update the matching rows.
332
+ def update(*args)
333
+ execute_dui(update_sql(*args)){|c| c.affected_rows}
334
+ end
335
+
336
+ private
337
+
338
+ # Convert the type of v using the method in MYSQL_TYPES[type].
339
+ def convert_type(v, type)
340
+ if v
341
+ if type == 1 && Sequel.convert_tinyint_to_bool
342
+ # We special case tinyint here to avoid adding
343
+ # a method to an ancestor of Fixnum
344
+ v.to_i == 0 ? false : true
345
+ else
346
+ (t = MYSQL_TYPES[type]) ? v.send(t) : v
347
+ end
348
+ else
349
+ nil
350
+ end
351
+ end
352
+
353
+ # Set the :type option to :select if it hasn't been set.
354
+ def execute(sql, opts={}, &block)
355
+ super(sql, {:type=>:select}.merge(opts), &block)
356
+ end
357
+
358
+ # Set the :type option to :dui if it hasn't been set.
359
+ def execute_dui(sql, opts={}, &block)
360
+ super(sql, {:type=>:dui}.merge(opts), &block)
361
+ end
362
+
363
+ # Extend the dataset with the MySQL stored procedure methods.
364
+ def prepare_extend_sproc(ds)
365
+ ds.extend(StoredProcedureMethods)
366
+ end
367
+ end
368
+ end
369
+ end
@@ -0,0 +1,174 @@
1
+ require 'odbc'
2
+
3
+ module Sequel
4
+ module ODBC
5
+ class Database < Sequel::Database
6
+ set_adapter_scheme :odbc
7
+
8
+ GUARDED_DRV_NAME = /^\{.+\}$/.freeze
9
+ DRV_NAME_GUARDS = '{%s}'.freeze
10
+
11
+ def initialize(opts)
12
+ super(opts)
13
+ case opts[:db_type]
14
+ when 'mssql'
15
+ require 'sequel_core/adapters/shared/mssql'
16
+ extend Sequel::MSSQL::DatabaseMethods
17
+ when 'progress'
18
+ require 'sequel_core/adapters/shared/progress'
19
+ extend Sequel::Progress::DatabaseMethods
20
+ end
21
+ end
22
+
23
+ def connect(server)
24
+ opts = server_opts(server)
25
+ if opts.include? :driver
26
+ drv = ::ODBC::Driver.new
27
+ drv.name = 'Sequel ODBC Driver130'
28
+ opts.each do |param, value|
29
+ if :driver == param and not (value =~ GUARDED_DRV_NAME)
30
+ value = DRV_NAME_GUARDS % value
31
+ end
32
+ drv.attrs[param.to_s.capitalize] = value
33
+ end
34
+ db = ::ODBC::Database.new
35
+ conn = db.drvconnect(drv)
36
+ else
37
+ conn = ::ODBC::connect(opts[:database], opts[:user], opts[:password])
38
+ end
39
+ conn.autocommit = true
40
+ conn
41
+ end
42
+
43
+ def dataset(opts = nil)
44
+ ODBC::Dataset.new(self, opts)
45
+ end
46
+
47
+ # ODBC returns native statement objects, which must be dropped if
48
+ # you call execute manually, or you will get warnings. See the
49
+ # fetch_rows method source code for an example of how to drop
50
+ # the statements.
51
+ def execute(sql, opts={})
52
+ log_info(sql)
53
+ synchronize(opts[:server]) do |conn|
54
+ r = conn.run(sql)
55
+ yield(r) if block_given?
56
+ r
57
+ end
58
+ end
59
+
60
+ def execute_dui(sql, opts={})
61
+ log_info(sql)
62
+ synchronize(opts[:server]){|conn| conn.do(sql)}
63
+ end
64
+ alias_method :do, :execute_dui
65
+
66
+ # Support single level transactions on ODBC
67
+ def transaction(server=nil)
68
+ synchronize(server) do |conn|
69
+ return yield(conn) if @transactions.include?(Thread.current)
70
+ log_info(begin_transaction_sql)
71
+ conn.do(begin_transaction_sql)
72
+ begin
73
+ @transactions << Thread.current
74
+ yield(conn)
75
+ rescue ::Exception => e
76
+ log_info(rollback_transaction_sql)
77
+ conn.do(rollback_transaction_sql)
78
+ transaction_error(e)
79
+ ensure
80
+ unless e
81
+ log_info(commit_transaction_sql)
82
+ conn.do(commit_transaction_sql)
83
+ end
84
+ @transactions.delete(Thread.current)
85
+ end
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def disconnect_connection(c)
92
+ c.disconnect
93
+ end
94
+ end
95
+
96
+ class Dataset < Sequel::Dataset
97
+ BOOL_TRUE = '1'.freeze
98
+ BOOL_FALSE = '0'.freeze
99
+ ODBC_TIMESTAMP_FORMAT = "{ts '%Y-%m-%d %H:%M:%S'}".freeze
100
+ ODBC_TIMESTAMP_AFTER_SECONDS =
101
+ ODBC_TIMESTAMP_FORMAT.index( '%S' ).succ - ODBC_TIMESTAMP_FORMAT.length
102
+ ODBC_DATE_FORMAT = "{d '%Y-%m-%d'}".freeze
103
+
104
+ def literal(v)
105
+ case v
106
+ when true
107
+ BOOL_TRUE
108
+ when false
109
+ BOOL_FALSE
110
+ when Time, DateTime
111
+ formatted = v.strftime(ODBC_TIMESTAMP_FORMAT)
112
+ usec = (Time === v ? v.usec : (v.sec_fraction * 86400000000))
113
+ formatted.insert(ODBC_TIMESTAMP_AFTER_SECONDS, ".#{(usec.to_f/1000).round}") if usec >= 1000
114
+ formatted
115
+ when Date
116
+ v.strftime(ODBC_DATE_FORMAT)
117
+ else
118
+ super
119
+ end
120
+ end
121
+
122
+ UNTITLED_COLUMN = 'untitled_%d'.freeze
123
+
124
+ def fetch_rows(sql, &block)
125
+ execute(sql) do |s|
126
+ begin
127
+ untitled_count = 0
128
+ @columns = s.columns(true).map do |c|
129
+ if (n = c.name).empty?
130
+ n = UNTITLED_COLUMN % (untitled_count += 1)
131
+ end
132
+ output_identifier(n)
133
+ end
134
+ rows = s.fetch_all
135
+ rows.each {|row| yield hash_row(row)} if rows
136
+ ensure
137
+ s.drop unless s.nil? rescue nil
138
+ end
139
+ end
140
+ self
141
+ end
142
+
143
+ private
144
+
145
+ def hash_row(row)
146
+ hash = {}
147
+ row.each_with_index do |v, idx|
148
+ hash[@columns[idx]] = convert_odbc_value(v)
149
+ end
150
+ hash
151
+ end
152
+
153
+ def convert_odbc_value(v)
154
+ # When fetching a result set, the Ruby ODBC driver converts all ODBC
155
+ # SQL types to an equivalent Ruby type; with the exception of
156
+ # SQL_TYPE_DATE, SQL_TYPE_TIME and SQL_TYPE_TIMESTAMP.
157
+ #
158
+ # The conversions below are consistent with the mappings in
159
+ # ODBCColumn#mapSqlTypeToGenericType and Column#klass.
160
+ case v
161
+ when ::ODBC::TimeStamp
162
+ DateTime.new(v.year, v.month, v.day, v.hour, v.minute, v.second)
163
+ when ::ODBC::Time
164
+ now = DateTime.now
165
+ Time.gm(now.year, now.month, now.day, v.hour, v.minute, v.second)
166
+ when ::ODBC::Date
167
+ Date.new(v.year, v.month, v.day)
168
+ else
169
+ v
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,68 @@
1
+ require 'openbase'
2
+
3
+ module Sequel
4
+ module OpenBase
5
+ class Database < Sequel::Database
6
+ set_adapter_scheme :openbase
7
+
8
+ def connect(server)
9
+ opts = server_opts(server)
10
+ OpenBase.new(
11
+ opts[:database],
12
+ opts[:host] || 'localhost',
13
+ opts[:user],
14
+ opts[:password]
15
+ )
16
+ end
17
+
18
+ def dataset(opts = nil)
19
+ OpenBase::Dataset.new(self, opts)
20
+ end
21
+
22
+ def execute(sql, opts={})
23
+ log_info(sql)
24
+ synchronize(opts[:server]) do |conn|
25
+ r = conn.execute(sql)
26
+ yield(r) if block_given?
27
+ r
28
+ end
29
+ end
30
+ alias_method :do, :execute
31
+
32
+ private
33
+
34
+ def disconnect_connection(c)
35
+ c.disconnect
36
+ end
37
+ end
38
+
39
+ class Dataset < Sequel::Dataset
40
+ def literal(v)
41
+ case v
42
+ when Time
43
+ literal(v.iso8601)
44
+ when Date, DateTime
45
+ literal(v.to_s)
46
+ else
47
+ super
48
+ end
49
+ end
50
+
51
+ def fetch_rows(sql)
52
+ execute(sql) do |result|
53
+ begin
54
+ @columns = result.column_infos.map{|c| output_identifier(c.name)}
55
+ result.each do |r|
56
+ row = {}
57
+ r.each_with_index {|v, i| row[@columns[i]] = v}
58
+ yield row
59
+ end
60
+ ensure
61
+ # result.close
62
+ end
63
+ end
64
+ self
65
+ end
66
+ end
67
+ end
68
+ end