epugh-sequel 0.0.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 (134) hide show
  1. data/README.rdoc +652 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/sequel +104 -0
  4. data/lib/sequel.rb +1 -0
  5. data/lib/sequel/adapters/ado.rb +85 -0
  6. data/lib/sequel/adapters/db2.rb +132 -0
  7. data/lib/sequel/adapters/dbi.rb +101 -0
  8. data/lib/sequel/adapters/do.rb +197 -0
  9. data/lib/sequel/adapters/do/mysql.rb +38 -0
  10. data/lib/sequel/adapters/do/postgres.rb +92 -0
  11. data/lib/sequel/adapters/do/sqlite.rb +31 -0
  12. data/lib/sequel/adapters/firebird.rb +307 -0
  13. data/lib/sequel/adapters/informix.rb +75 -0
  14. data/lib/sequel/adapters/jdbc.rb +485 -0
  15. data/lib/sequel/adapters/jdbc/h2.rb +62 -0
  16. data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
  18. data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
  19. data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
  20. data/lib/sequel/adapters/mysql.rb +370 -0
  21. data/lib/sequel/adapters/odbc.rb +184 -0
  22. data/lib/sequel/adapters/openbase.rb +57 -0
  23. data/lib/sequel/adapters/oracle.rb +140 -0
  24. data/lib/sequel/adapters/postgres.rb +453 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +93 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +341 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +62 -0
  28. data/lib/sequel/adapters/shared/postgres.rb +743 -0
  29. data/lib/sequel/adapters/shared/progress.rb +34 -0
  30. data/lib/sequel/adapters/shared/sqlite.rb +263 -0
  31. data/lib/sequel/adapters/sqlite.rb +243 -0
  32. data/lib/sequel/adapters/utils/date_format.rb +21 -0
  33. data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
  34. data/lib/sequel/adapters/utils/unsupported.rb +62 -0
  35. data/lib/sequel/connection_pool.rb +258 -0
  36. data/lib/sequel/core.rb +204 -0
  37. data/lib/sequel/core_sql.rb +185 -0
  38. data/lib/sequel/database.rb +687 -0
  39. data/lib/sequel/database/schema_generator.rb +324 -0
  40. data/lib/sequel/database/schema_methods.rb +164 -0
  41. data/lib/sequel/database/schema_sql.rb +324 -0
  42. data/lib/sequel/dataset.rb +422 -0
  43. data/lib/sequel/dataset/convenience.rb +237 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +220 -0
  45. data/lib/sequel/dataset/sql.rb +1105 -0
  46. data/lib/sequel/deprecated.rb +529 -0
  47. data/lib/sequel/exceptions.rb +44 -0
  48. data/lib/sequel/extensions/blank.rb +42 -0
  49. data/lib/sequel/extensions/inflector.rb +288 -0
  50. data/lib/sequel/extensions/pagination.rb +96 -0
  51. data/lib/sequel/extensions/pretty_table.rb +78 -0
  52. data/lib/sequel/extensions/query.rb +48 -0
  53. data/lib/sequel/extensions/string_date_time.rb +47 -0
  54. data/lib/sequel/metaprogramming.rb +44 -0
  55. data/lib/sequel/migration.rb +212 -0
  56. data/lib/sequel/model.rb +142 -0
  57. data/lib/sequel/model/association_reflection.rb +263 -0
  58. data/lib/sequel/model/associations.rb +1024 -0
  59. data/lib/sequel/model/base.rb +911 -0
  60. data/lib/sequel/model/deprecated.rb +188 -0
  61. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  62. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  63. data/lib/sequel/model/deprecated_validations.rb +384 -0
  64. data/lib/sequel/model/errors.rb +37 -0
  65. data/lib/sequel/model/exceptions.rb +7 -0
  66. data/lib/sequel/model/inflections.rb +230 -0
  67. data/lib/sequel/model/plugins.rb +74 -0
  68. data/lib/sequel/object_graph.rb +230 -0
  69. data/lib/sequel/plugins/caching.rb +122 -0
  70. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  71. data/lib/sequel/plugins/schema.rb +53 -0
  72. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  73. data/lib/sequel/plugins/validation_class_methods.rb +373 -0
  74. data/lib/sequel/sql.rb +854 -0
  75. data/lib/sequel/version.rb +11 -0
  76. data/lib/sequel_core.rb +1 -0
  77. data/lib/sequel_model.rb +1 -0
  78. data/spec/adapters/ado_spec.rb +46 -0
  79. data/spec/adapters/firebird_spec.rb +376 -0
  80. data/spec/adapters/informix_spec.rb +96 -0
  81. data/spec/adapters/mysql_spec.rb +875 -0
  82. data/spec/adapters/oracle_spec.rb +272 -0
  83. data/spec/adapters/postgres_spec.rb +692 -0
  84. data/spec/adapters/spec_helper.rb +10 -0
  85. data/spec/adapters/sqlite_spec.rb +550 -0
  86. data/spec/core/connection_pool_spec.rb +526 -0
  87. data/spec/core/core_ext_spec.rb +156 -0
  88. data/spec/core/core_sql_spec.rb +528 -0
  89. data/spec/core/database_spec.rb +1214 -0
  90. data/spec/core/dataset_spec.rb +3513 -0
  91. data/spec/core/expression_filters_spec.rb +363 -0
  92. data/spec/core/migration_spec.rb +261 -0
  93. data/spec/core/object_graph_spec.rb +280 -0
  94. data/spec/core/pretty_table_spec.rb +58 -0
  95. data/spec/core/schema_generator_spec.rb +167 -0
  96. data/spec/core/schema_spec.rb +778 -0
  97. data/spec/core/spec_helper.rb +82 -0
  98. data/spec/core/version_spec.rb +7 -0
  99. data/spec/extensions/blank_spec.rb +67 -0
  100. data/spec/extensions/caching_spec.rb +201 -0
  101. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  102. data/spec/extensions/inflector_spec.rb +122 -0
  103. data/spec/extensions/pagination_spec.rb +99 -0
  104. data/spec/extensions/pretty_table_spec.rb +91 -0
  105. data/spec/extensions/query_spec.rb +85 -0
  106. data/spec/extensions/schema_spec.rb +111 -0
  107. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  108. data/spec/extensions/spec_helper.rb +90 -0
  109. data/spec/extensions/string_date_time_spec.rb +93 -0
  110. data/spec/extensions/validation_class_methods_spec.rb +1054 -0
  111. data/spec/integration/dataset_test.rb +160 -0
  112. data/spec/integration/eager_loader_test.rb +683 -0
  113. data/spec/integration/prepared_statement_test.rb +130 -0
  114. data/spec/integration/schema_test.rb +183 -0
  115. data/spec/integration/spec_helper.rb +75 -0
  116. data/spec/integration/type_test.rb +96 -0
  117. data/spec/model/association_reflection_spec.rb +93 -0
  118. data/spec/model/associations_spec.rb +1780 -0
  119. data/spec/model/base_spec.rb +494 -0
  120. data/spec/model/caching_spec.rb +217 -0
  121. data/spec/model/dataset_methods_spec.rb +78 -0
  122. data/spec/model/eager_loading_spec.rb +1165 -0
  123. data/spec/model/hooks_spec.rb +472 -0
  124. data/spec/model/inflector_spec.rb +126 -0
  125. data/spec/model/model_spec.rb +588 -0
  126. data/spec/model/plugins_spec.rb +142 -0
  127. data/spec/model/record_spec.rb +1243 -0
  128. data/spec/model/schema_spec.rb +92 -0
  129. data/spec/model/spec_helper.rb +124 -0
  130. data/spec/model/validations_spec.rb +1080 -0
  131. data/spec/rcov.opts +6 -0
  132. data/spec/spec.opts +0 -0
  133. data/spec/spec_config.rb.example +10 -0
  134. metadata +202 -0
@@ -0,0 +1,62 @@
1
+ Sequel.require %w'date_format unsupported', 'adapters/utils'
2
+
3
+ module Sequel
4
+ module JDBC
5
+ # Database and Dataset support for H2 databases accessed via JDBC.
6
+ module H2
7
+ # Instance methods for H2 Database objects accessed via JDBC.
8
+ module DatabaseMethods
9
+ # H2 needs to add a primary key column as a constraint
10
+ def alter_table_sql(table, op)
11
+ case op[:op]
12
+ when :add_column
13
+ if op.delete(:primary_key)
14
+ sql = super(table, op)
15
+ [sql, "ALTER TABLE #{quote_schema_table(table)} ADD PRIMARY KEY (#{quote_identifier(op[:name])})"]
16
+ else
17
+ super(table, op)
18
+ end
19
+ else
20
+ super(table, op)
21
+ end
22
+ end
23
+
24
+ # Return Sequel::JDBC::H2::Dataset object with the given opts.
25
+ def dataset(opts=nil)
26
+ Sequel::JDBC::H2::Dataset.new(self, opts)
27
+ end
28
+
29
+ # H2 uses an IDENTITY type
30
+ def serial_primary_key_options
31
+ {:primary_key => true, :type => :identity}
32
+ end
33
+
34
+ private
35
+
36
+ # Use IDENTITY() to get the last inserted id.
37
+ def last_insert_id(conn, opts={})
38
+ stmt = conn.createStatement
39
+ begin
40
+ rs = stmt.executeQuery('SELECT IDENTITY();')
41
+ rs.next
42
+ rs.getInt(1)
43
+ ensure
44
+ stmt.close
45
+ end
46
+ end
47
+
48
+ # Default to a single connection for a memory database.
49
+ def connection_pool_default_options
50
+ o = super
51
+ uri == 'jdbc:h2:mem:' ? o.merge(:max_connections=>1) : o
52
+ end
53
+ end
54
+
55
+ # Dataset class for H2 datasets accessed via JDBC.
56
+ class Dataset < JDBC::Dataset
57
+ include Dataset::SQLStandardDateFormat
58
+ include Dataset::UnsupportedIsTrue
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,56 @@
1
+ Sequel.require 'adapters/shared/mysql'
2
+
3
+ module Sequel
4
+ module JDBC
5
+ # Database and Dataset instance methods for MySQL specific
6
+ # support via JDBC.
7
+ module MySQL
8
+ # Database instance methods for MySQL databases accessed via JDBC.
9
+ module DatabaseMethods
10
+ include Sequel::MySQL::DatabaseMethods
11
+
12
+ # Return instance of Sequel::JDBC::MySQL::Dataset with the given opts.
13
+ def dataset(opts=nil)
14
+ Sequel::JDBC::MySQL::Dataset.new(self, opts)
15
+ end
16
+
17
+ private
18
+
19
+ # The database name for the given database. Need to parse it out
20
+ # of the connection string, since the JDBC does no parsing on the
21
+ # given connection string by default.
22
+ def database_name
23
+ u = URI.parse(uri.sub(/\Ajdbc:/, ''))
24
+ (m = /\/(.*)/.match(u.path)) && m[1]
25
+ end
26
+
27
+ # Get the last inserted id using LAST_INSERT_ID().
28
+ def last_insert_id(conn, opts={})
29
+ stmt = conn.createStatement
30
+ begin
31
+ rs = stmt.executeQuery('SELECT LAST_INSERT_ID()')
32
+ rs.next
33
+ rs.getInt(1)
34
+ ensure
35
+ stmt.close
36
+ end
37
+ end
38
+ end
39
+
40
+ # Dataset class for MySQL datasets accessed via JDBC.
41
+ class Dataset < JDBC::Dataset
42
+ include Sequel::MySQL::DatasetMethods
43
+
44
+ # Use execute_insert to execute the insert_sql.
45
+ def insert(*values)
46
+ execute_insert(insert_sql(*values))
47
+ end
48
+
49
+ # Use execute_insert to execute the replace_sql.
50
+ def replace(*args)
51
+ execute_insert(replace_sql(*args))
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,23 @@
1
+ Sequel.require 'adapters/shared/oracle'
2
+
3
+ module Sequel
4
+ module JDBC
5
+ # Database and Dataset support for Oracle databases accessed via JDBC.
6
+ module Oracle
7
+ # Instance methods for Oracle Database objects accessed via JDBC.
8
+ module DatabaseMethods
9
+ include Sequel::Oracle::DatabaseMethods
10
+
11
+ # Return Sequel::JDBC::Oracle::Dataset object with the given opts.
12
+ def dataset(opts=nil)
13
+ Sequel::JDBC::Oracle::Dataset.new(self, opts)
14
+ end
15
+ end
16
+
17
+ # Dataset class for Oracle datasets accessed via JDBC.
18
+ class Dataset < JDBC::Dataset
19
+ include Sequel::Oracle::DatasetMethods
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,101 @@
1
+ Sequel.require 'adapters/shared/postgres'
2
+
3
+ module Sequel
4
+ Postgres::CONVERTED_EXCEPTIONS << NativeException
5
+
6
+ module JDBC
7
+ # Adapter, Database, and Dataset support for accessing a PostgreSQL
8
+ # database via JDBC.
9
+ module Postgres
10
+ # Methods to add to the JDBC adapter/connection to allow it to work
11
+ # with the shared PostgreSQL code.
12
+ module AdapterMethods
13
+ include Sequel::Postgres::AdapterMethods
14
+
15
+ # Give the JDBC adapter a direct execute method, which creates
16
+ # a statement with the given sql and executes it.
17
+ def execute(sql, args=nil)
18
+ method = block_given? ? :executeQuery : :execute
19
+ stmt = createStatement
20
+ begin
21
+ rows = stmt.send(method, sql)
22
+ yield(rows) if block_given?
23
+ rescue NativeException => e
24
+ raise_error(e)
25
+ ensure
26
+ stmt.close
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ # JDBC specific method of getting specific values from a result set.
33
+ def single_value(r)
34
+ unless r.nil?
35
+ r.next
36
+ r.getString(1) unless r.getRow == 0
37
+ end
38
+ end
39
+ end
40
+
41
+ # Methods to add to Database instances that access PostgreSQL via
42
+ # JDBC.
43
+ module DatabaseMethods
44
+ include Sequel::Postgres::DatabaseMethods
45
+
46
+ # Add the primary_keys and primary_key_sequences instance variables,
47
+ # so we can get the correct return values for inserted rows.
48
+ def self.extended(db)
49
+ db.instance_eval do
50
+ @primary_keys = {}
51
+ @primary_key_sequences = {}
52
+ end
53
+ end
54
+
55
+ # Return instance of Sequel::JDBC::Postgres::Dataset with the given opts.
56
+ def dataset(opts=nil)
57
+ Sequel::JDBC::Postgres::Dataset.new(self, opts)
58
+ end
59
+
60
+ # Run the INSERT sql on the database and return the primary key
61
+ # for the record.
62
+ def execute_insert(sql, opts={})
63
+ super(sql, {:type=>:insert}.merge(opts))
64
+ end
65
+
66
+ private
67
+
68
+ # Extend the adapter with the JDBC PostgreSQL AdapterMethods
69
+ def setup_connection(conn)
70
+ conn = super(conn)
71
+ conn.extend(Sequel::JDBC::Postgres::AdapterMethods)
72
+ conn.db = self
73
+ conn.apply_connection_settings
74
+ conn
75
+ end
76
+
77
+ # Call insert_result with the table and values specified in the opts.
78
+ def last_insert_id(conn, opts)
79
+ insert_result(conn, opts[:table], opts[:values])
80
+ end
81
+ end
82
+
83
+ # Dataset subclass used for datasets that connect to PostgreSQL via JDBC.
84
+ class Dataset < JDBC::Dataset
85
+ include Sequel::Postgres::DatasetMethods
86
+
87
+ # Add the shared PostgreSQL prepared statement methods
88
+ def prepare(*args)
89
+ ps = super
90
+ ps.extend(::Sequel::Postgres::DatasetMethods::PreparedStatementMethods)
91
+ ps
92
+ end
93
+
94
+ # Literalize strings similar to the native postgres adapter
95
+ def literal_string(v)
96
+ db.synchronize{|c| "'#{c.escape_string(v)}'"}
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,43 @@
1
+ Sequel.require 'adapters/shared/sqlite'
2
+
3
+ module Sequel
4
+ module JDBC
5
+ # Database and Dataset support for SQLite databases accessed via JDBC.
6
+ module SQLite
7
+ # Instance methods for SQLite Database objects accessed via JDBC.
8
+ module DatabaseMethods
9
+ include Sequel::SQLite::DatabaseMethods
10
+
11
+ # Return Sequel::JDBC::SQLite::Dataset object with the given opts.
12
+ def dataset(opts=nil)
13
+ Sequel::JDBC::SQLite::Dataset.new(self, opts)
14
+ end
15
+
16
+ private
17
+
18
+ # Use last_insert_rowid() to get the last inserted id.
19
+ def last_insert_id(conn, opts={})
20
+ stmt = conn.createStatement
21
+ begin
22
+ rs = stmt.executeQuery('SELECT last_insert_rowid()')
23
+ rs.next
24
+ rs.getInt(1)
25
+ ensure
26
+ stmt.close
27
+ end
28
+ end
29
+
30
+ # Default to a single connection for a memory database.
31
+ def connection_pool_default_options
32
+ o = super
33
+ uri == 'jdbc:sqlite::memory:' ? o.merge(:max_connections=>1) : o
34
+ end
35
+ end
36
+
37
+ # Dataset class for SQLite datasets accessed via JDBC.
38
+ class Dataset < JDBC::Dataset
39
+ include Sequel::SQLite::DatasetMethods
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,370 @@
1
+ require 'mysql'
2
+ Sequel.require %w'shared/mysql utils/stored_procedures', 'adapters'
3
+
4
+ module Sequel
5
+ # Module for holding all MySQL-related classes and modules for Sequel.
6
+ module MySQL
7
+ # Mapping of type numbers to conversion procs
8
+ MYSQL_TYPES = {}
9
+
10
+ # Use only a single proc for each type to save on memory
11
+ MYSQL_TYPE_PROCS = {
12
+ [0, 246] => lambda{|v| BigDecimal.new(v)}, # decimal
13
+ [1, 2, 3, 8, 9, 13, 247, 248] => lambda{|v| v.to_i}, # integer
14
+ [4, 5] => lambda{|v| v.to_f}, # float
15
+ [10, 14] => lambda{|v| @zero_date_time_behavior_convert_to_null ? trap_bad_date_time(v) : Sequel.string_to_date(v)}, # date
16
+ [7, 12] => lambda{|v| @zero_date_time_behavior_convert_to_null ? trap_bad_date_time(v) : Sequel.string_to_datetime(v)}, # datetime
17
+ [11] => lambda{|v| @zero_date_time_behavior_convert_to_null ? trap_bad_date_time(v) : Sequel.string_to_time(v)}, # time
18
+ [249, 250, 251, 252] => lambda{|v| Sequel::SQL::Blob.new(v)} # blob
19
+ }
20
+ MYSQL_TYPE_PROCS.each do |k,v|
21
+ k.each{|n| MYSQL_TYPES[n] = v}
22
+ end
23
+
24
+ @zero_date_time_behavior_convert_to_null = false
25
+
26
+ class << self
27
+ # Similar to the JDBC parameter zeroDateTimeBehavior=convertToNull, handles
28
+ # dates 0000-00-00 and times like 00:00:00 and converts to null
29
+ attr_accessor :zero_date_time_behavior_convert_to_null
30
+
31
+ def trap_bad_date_time(v)
32
+ result = nil
33
+ begin
34
+ result = Sequel.string_to_date(v)
35
+ rescue Sequel::Error::InvalidValue
36
+ end
37
+ result
38
+ end
39
+ end
40
+
41
+ # Database class for MySQL databases used with Sequel.
42
+ class Database < Sequel::Database
43
+ include Sequel::MySQL::DatabaseMethods
44
+
45
+ set_adapter_scheme :mysql
46
+
47
+ # Support stored procedures on MySQL
48
+ def call_sproc(name, opts={}, &block)
49
+ args = opts[:args] || []
50
+ execute("CALL #{name}#{args.empty? ? '()' : literal(args)}", opts.merge(:sproc=>false), &block)
51
+ end
52
+
53
+ # Connect to the database. In addition to the usual database options,
54
+ # the following options have effect:
55
+ #
56
+ # * :auto_is_null - Set to true to use MySQL default behavior of having
57
+ # a filter for an autoincrement column equals NULL to return the last
58
+ # inserted row.
59
+ # * :charset - Same as :encoding (:encoding takes precendence)
60
+ # * :compress - Set to false to not compress results from the server
61
+ # * :encoding - Set all the related character sets for this
62
+ # connection (connection, client, database, server, and results).
63
+ # * :socket - Use a unix socket file instead of connecting via TCP/IP.
64
+ # * :timeout - Set the timeout in seconds before the server will
65
+ # disconnect this connection.
66
+ def connect(server)
67
+ opts = server_opts(server)
68
+ conn = Mysql.init
69
+ conn.options(Mysql::OPT_LOCAL_INFILE, "client")
70
+ if encoding = opts[:encoding] || opts[:charset]
71
+ # set charset _before_ the connect. using an option instead of "SET (NAMES|CHARACTER_SET_*)" works across reconnects
72
+ conn.options(Mysql::SET_CHARSET_NAME, encoding)
73
+ end
74
+ conn.real_connect(
75
+ opts[:host] || 'localhost',
76
+ opts[:user],
77
+ opts[:password],
78
+ opts[:database],
79
+ opts[:port],
80
+ opts[:socket],
81
+ Mysql::CLIENT_MULTI_RESULTS +
82
+ Mysql::CLIENT_MULTI_STATEMENTS +
83
+ (opts[:compress] == false ? 0 : Mysql::CLIENT_COMPRESS)
84
+ )
85
+
86
+ # increase timeout so mysql server doesn't disconnect us
87
+ conn.query("set @@wait_timeout = #{opts[:timeout] || 2592000}")
88
+
89
+ # By default, MySQL 'where id is null' selects the last inserted id
90
+ conn.query("set SQL_AUTO_IS_NULL=0") unless opts[:auto_is_null]
91
+
92
+ conn.query_with_result = false
93
+ class << conn
94
+ attr_accessor :prepared_statements
95
+ end
96
+ conn.prepared_statements = {}
97
+ conn.reconnect = true
98
+ conn
99
+ end
100
+
101
+ # Returns instance of Sequel::MySQL::Dataset with the given options.
102
+ def dataset(opts = nil)
103
+ MySQL::Dataset.new(self, opts)
104
+ end
105
+
106
+ # Executes the given SQL using an available connection, yielding the
107
+ # connection if the block is given.
108
+ def execute(sql, opts={}, &block)
109
+ return call_sproc(sql, opts, &block) if opts[:sproc]
110
+ return execute_prepared_statement(sql, opts, &block) if Symbol === sql
111
+ begin
112
+ synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
113
+ rescue Mysql::Error => e
114
+ raise_error(e)
115
+ end
116
+ end
117
+
118
+ # Return the version of the MySQL server two which we are connecting.
119
+ def server_version(server=nil)
120
+ @server_version ||= (synchronize(server){|conn| conn.server_version if conn.respond_to?(:server_version)} || super)
121
+ end
122
+
123
+ # Support single level transactions on MySQL.
124
+ def transaction(opts={})
125
+ unless opts.is_a?(Hash)
126
+ Deprecation.deprecate('Passing an argument other than a Hash to Database#transaction', "Use DB.transaction(:server=>#{opts.inspect})")
127
+ opts = {:server=>opts}
128
+ end
129
+ synchronize(opts[:server]) do |conn|
130
+ return yield(conn) if @transactions.include?(Thread.current)
131
+ log_info(begin_transaction_sql)
132
+ conn.query(begin_transaction_sql)
133
+ begin
134
+ @transactions << Thread.current
135
+ yield(conn)
136
+ rescue ::Exception => e
137
+ log_info(rollback_transaction_sql)
138
+ conn.query(rollback_transaction_sql)
139
+ transaction_error(e, Mysql::Error)
140
+ ensure
141
+ unless e
142
+ log_info(commit_transaction_sql)
143
+ conn.query(commit_transaction_sql)
144
+ end
145
+ @transactions.delete(Thread.current)
146
+ end
147
+ end
148
+ end
149
+
150
+ private
151
+
152
+ # Execute the given SQL on the given connection. If the :type
153
+ # option is :select, yield the result of the query, otherwise
154
+ # yield the connection if a block is given.
155
+ def _execute(conn, sql, opts)
156
+ log_info(sql)
157
+ conn.query(sql)
158
+ if opts[:type] == :select
159
+ loop do
160
+ begin
161
+ r = conn.use_result
162
+ rescue Mysql::Error
163
+ nil
164
+ else
165
+ begin
166
+ yield r
167
+ ensure
168
+ r.free
169
+ end
170
+ end
171
+ break unless conn.respond_to?(:next_result) && conn.next_result
172
+ end
173
+ else
174
+ yield conn if block_given?
175
+ end
176
+ end
177
+
178
+ # MySQL doesn't need the connection pool to convert exceptions.
179
+ def connection_pool_default_options
180
+ super.merge(:pool_convert_exceptions=>false)
181
+ end
182
+
183
+ # The database name when using the native adapter is always stored in
184
+ # the :database option.
185
+ def database_name
186
+ @opts[:database]
187
+ end
188
+
189
+ # Closes given database connection.
190
+ def disconnect_connection(c)
191
+ c.close
192
+ end
193
+
194
+ # Executes a prepared statement on an available connection. If the
195
+ # prepared statement already exists for the connection and has the same
196
+ # SQL, reuse it, otherwise, prepare the new statement. Because of the
197
+ # usual MySQL stupidity, we are forced to name arguments via separate
198
+ # SET queries. Use @sequel_arg_N (for N starting at 1) for these
199
+ # arguments.
200
+ def execute_prepared_statement(ps_name, opts, &block)
201
+ args = opts[:arguments]
202
+ ps = prepared_statements[ps_name]
203
+ sql = ps.prepared_sql
204
+ synchronize(opts[:server]) do |conn|
205
+ unless conn.prepared_statements[ps_name] == sql
206
+ conn.prepared_statements[ps_name] = sql
207
+ s = "PREPARE #{ps_name} FROM '#{::Mysql.quote(sql)}'"
208
+ log_info(s)
209
+ conn.query(s)
210
+ end
211
+ i = 0
212
+ args.each do |arg|
213
+ s = "SET @sequel_arg_#{i+=1} = #{literal(arg)}"
214
+ log_info(s)
215
+ conn.query(s)
216
+ end
217
+ _execute(conn, "EXECUTE #{ps_name}#{" USING #{(1..i).map{|j| "@sequel_arg_#{j}"}.join(', ')}" unless i == 0}", opts, &block)
218
+ end
219
+ end
220
+ end
221
+
222
+ # Dataset class for MySQL datasets accessed via the native driver.
223
+ class Dataset < Sequel::Dataset
224
+ include Sequel::MySQL::DatasetMethods
225
+ include StoredProcedures
226
+
227
+ # Methods to add to MySQL prepared statement calls without using a
228
+ # real database prepared statement and bound variables.
229
+ module CallableStatementMethods
230
+ # Extend given dataset with this module so subselects inside subselects in
231
+ # prepared statements work.
232
+ def subselect_sql(ds)
233
+ ps = ds.to_prepared_statement(:select)
234
+ ps.extend(CallableStatementMethods)
235
+ ps.prepared_args = prepared_args
236
+ ps.prepared_sql
237
+ end
238
+ end
239
+
240
+ # Methods for MySQL prepared statements using the native driver.
241
+ module PreparedStatementMethods
242
+ include Sequel::Dataset::UnnumberedArgumentMapper
243
+
244
+ private
245
+
246
+ # Execute the prepared statement with the bind arguments instead of
247
+ # the given SQL.
248
+ def execute(sql, opts={}, &block)
249
+ super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
250
+ end
251
+
252
+ # Same as execute, explicit due to intricacies of alias and super.
253
+ def execute_dui(sql, opts={}, &block)
254
+ super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
255
+ end
256
+ end
257
+
258
+ # Methods for MySQL stored procedures using the native driver.
259
+ module StoredProcedureMethods
260
+ include Sequel::Dataset::StoredProcedureMethods
261
+
262
+ private
263
+
264
+ # Execute the database stored procedure with the stored arguments.
265
+ def execute(sql, opts={}, &block)
266
+ super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
267
+ end
268
+
269
+ # Same as execute, explicit due to intricacies of alias and super.
270
+ def execute_dui(sql, opts={}, &block)
271
+ super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
272
+ end
273
+ end
274
+
275
+ # MySQL is different in that it supports prepared statements but not bound
276
+ # variables outside of prepared statements. The default implementation
277
+ # breaks the use of subselects in prepared statements, so extend the
278
+ # temporary prepared statement that this creates with a module that
279
+ # fixes it.
280
+ def call(type, bind_arguments={}, values=nil)
281
+ ps = to_prepared_statement(type, values)
282
+ ps.extend(CallableStatementMethods)
283
+ ps.call(bind_arguments)
284
+ end
285
+
286
+ # Delete rows matching this dataset
287
+ def delete(opts = (defarg=true;nil))
288
+ execute_dui(defarg ? delete_sql : delete_sql(opts)){|c| c.affected_rows}
289
+ end
290
+
291
+ # Yield all rows matching this dataset
292
+ def fetch_rows(sql)
293
+ execute(sql) do |r|
294
+ column_types = []
295
+ @columns = r.fetch_fields.map{|f| column_types << f.type; output_identifier(f.name)}
296
+ while row = r.fetch_row
297
+ h = {}
298
+ @columns.each_with_index {|f, i| h[f] = convert_type(row[i], column_types[i])}
299
+ yield h
300
+ end
301
+ end
302
+ self
303
+ end
304
+
305
+ # Insert a new value into this dataset
306
+ def insert(*values)
307
+ execute_dui(insert_sql(*values)){|c| c.insert_id}
308
+ end
309
+
310
+ # Store the given type of prepared statement in the associated database
311
+ # with the given name.
312
+ def prepare(type, name=nil, values=nil)
313
+ ps = to_prepared_statement(type, values)
314
+ ps.extend(PreparedStatementMethods)
315
+ if name
316
+ ps.prepared_statement_name = name
317
+ db.prepared_statements[name] = ps
318
+ end
319
+ ps
320
+ end
321
+
322
+ # Replace (update or insert) the matching row.
323
+ def replace(*args)
324
+ execute_dui(replace_sql(*args)){|c| c.insert_id}
325
+ end
326
+
327
+ # Update the matching rows.
328
+ def update(values={}, opts=(defarg=true;nil))
329
+ execute_dui(defarg ? update_sql(values) : update_sql(values, opts)){|c| c.affected_rows}
330
+ end
331
+
332
+ private
333
+
334
+ # Convert the type of v using the method in MYSQL_TYPES[type].
335
+ def convert_type(v, type)
336
+ if v
337
+ if type == 1 && Sequel.convert_tinyint_to_bool
338
+ # We special case tinyint here to avoid adding
339
+ # a method to an ancestor of Fixnum
340
+ v.to_i == 0 ? false : true
341
+ else
342
+ (b = MYSQL_TYPES[type]) ? b.call(v) : v
343
+ end
344
+ else
345
+ nil
346
+ end
347
+ end
348
+
349
+ # Set the :type option to :select if it hasn't been set.
350
+ def execute(sql, opts={}, &block)
351
+ super(sql, {:type=>:select}.merge(opts), &block)
352
+ end
353
+
354
+ # Set the :type option to :dui if it hasn't been set.
355
+ def execute_dui(sql, opts={}, &block)
356
+ super(sql, {:type=>:dui}.merge(opts), &block)
357
+ end
358
+
359
+ # Handle correct quoting of strings using ::MySQL.quote.
360
+ def literal_string(v)
361
+ "'#{::Mysql.quote(v)}'"
362
+ end
363
+
364
+ # Extend the dataset with the MySQL stored procedure methods.
365
+ def prepare_extend_sproc(ds)
366
+ ds.extend(StoredProcedureMethods)
367
+ end
368
+ end
369
+ end
370
+ end