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.
- data/README.rdoc +652 -0
- data/VERSION.yml +4 -0
- data/bin/sequel +104 -0
- data/lib/sequel.rb +1 -0
- data/lib/sequel/adapters/ado.rb +85 -0
- data/lib/sequel/adapters/db2.rb +132 -0
- data/lib/sequel/adapters/dbi.rb +101 -0
- data/lib/sequel/adapters/do.rb +197 -0
- data/lib/sequel/adapters/do/mysql.rb +38 -0
- data/lib/sequel/adapters/do/postgres.rb +92 -0
- data/lib/sequel/adapters/do/sqlite.rb +31 -0
- data/lib/sequel/adapters/firebird.rb +307 -0
- data/lib/sequel/adapters/informix.rb +75 -0
- data/lib/sequel/adapters/jdbc.rb +485 -0
- data/lib/sequel/adapters/jdbc/h2.rb +62 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
- data/lib/sequel/adapters/mysql.rb +370 -0
- data/lib/sequel/adapters/odbc.rb +184 -0
- data/lib/sequel/adapters/openbase.rb +57 -0
- data/lib/sequel/adapters/oracle.rb +140 -0
- data/lib/sequel/adapters/postgres.rb +453 -0
- data/lib/sequel/adapters/shared/mssql.rb +93 -0
- data/lib/sequel/adapters/shared/mysql.rb +341 -0
- data/lib/sequel/adapters/shared/oracle.rb +62 -0
- data/lib/sequel/adapters/shared/postgres.rb +743 -0
- data/lib/sequel/adapters/shared/progress.rb +34 -0
- data/lib/sequel/adapters/shared/sqlite.rb +263 -0
- data/lib/sequel/adapters/sqlite.rb +243 -0
- data/lib/sequel/adapters/utils/date_format.rb +21 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
- data/lib/sequel/adapters/utils/unsupported.rb +62 -0
- data/lib/sequel/connection_pool.rb +258 -0
- data/lib/sequel/core.rb +204 -0
- data/lib/sequel/core_sql.rb +185 -0
- data/lib/sequel/database.rb +687 -0
- data/lib/sequel/database/schema_generator.rb +324 -0
- data/lib/sequel/database/schema_methods.rb +164 -0
- data/lib/sequel/database/schema_sql.rb +324 -0
- data/lib/sequel/dataset.rb +422 -0
- data/lib/sequel/dataset/convenience.rb +237 -0
- data/lib/sequel/dataset/prepared_statements.rb +220 -0
- data/lib/sequel/dataset/sql.rb +1105 -0
- data/lib/sequel/deprecated.rb +529 -0
- data/lib/sequel/exceptions.rb +44 -0
- data/lib/sequel/extensions/blank.rb +42 -0
- data/lib/sequel/extensions/inflector.rb +288 -0
- data/lib/sequel/extensions/pagination.rb +96 -0
- data/lib/sequel/extensions/pretty_table.rb +78 -0
- data/lib/sequel/extensions/query.rb +48 -0
- data/lib/sequel/extensions/string_date_time.rb +47 -0
- data/lib/sequel/metaprogramming.rb +44 -0
- data/lib/sequel/migration.rb +212 -0
- data/lib/sequel/model.rb +142 -0
- data/lib/sequel/model/association_reflection.rb +263 -0
- data/lib/sequel/model/associations.rb +1024 -0
- data/lib/sequel/model/base.rb +911 -0
- data/lib/sequel/model/deprecated.rb +188 -0
- data/lib/sequel/model/deprecated_hooks.rb +103 -0
- data/lib/sequel/model/deprecated_inflector.rb +335 -0
- data/lib/sequel/model/deprecated_validations.rb +384 -0
- data/lib/sequel/model/errors.rb +37 -0
- data/lib/sequel/model/exceptions.rb +7 -0
- data/lib/sequel/model/inflections.rb +230 -0
- data/lib/sequel/model/plugins.rb +74 -0
- data/lib/sequel/object_graph.rb +230 -0
- data/lib/sequel/plugins/caching.rb +122 -0
- data/lib/sequel/plugins/hook_class_methods.rb +122 -0
- data/lib/sequel/plugins/schema.rb +53 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
- data/lib/sequel/plugins/validation_class_methods.rb +373 -0
- data/lib/sequel/sql.rb +854 -0
- data/lib/sequel/version.rb +11 -0
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/ado_spec.rb +46 -0
- data/spec/adapters/firebird_spec.rb +376 -0
- data/spec/adapters/informix_spec.rb +96 -0
- data/spec/adapters/mysql_spec.rb +875 -0
- data/spec/adapters/oracle_spec.rb +272 -0
- data/spec/adapters/postgres_spec.rb +692 -0
- data/spec/adapters/spec_helper.rb +10 -0
- data/spec/adapters/sqlite_spec.rb +550 -0
- data/spec/core/connection_pool_spec.rb +526 -0
- data/spec/core/core_ext_spec.rb +156 -0
- data/spec/core/core_sql_spec.rb +528 -0
- data/spec/core/database_spec.rb +1214 -0
- data/spec/core/dataset_spec.rb +3513 -0
- data/spec/core/expression_filters_spec.rb +363 -0
- data/spec/core/migration_spec.rb +261 -0
- data/spec/core/object_graph_spec.rb +280 -0
- data/spec/core/pretty_table_spec.rb +58 -0
- data/spec/core/schema_generator_spec.rb +167 -0
- data/spec/core/schema_spec.rb +778 -0
- data/spec/core/spec_helper.rb +82 -0
- data/spec/core/version_spec.rb +7 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/caching_spec.rb +201 -0
- data/spec/extensions/hook_class_methods_spec.rb +470 -0
- data/spec/extensions/inflector_spec.rb +122 -0
- data/spec/extensions/pagination_spec.rb +99 -0
- data/spec/extensions/pretty_table_spec.rb +91 -0
- data/spec/extensions/query_spec.rb +85 -0
- data/spec/extensions/schema_spec.rb +111 -0
- data/spec/extensions/single_table_inheritance_spec.rb +53 -0
- data/spec/extensions/spec_helper.rb +90 -0
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/extensions/validation_class_methods_spec.rb +1054 -0
- data/spec/integration/dataset_test.rb +160 -0
- data/spec/integration/eager_loader_test.rb +683 -0
- data/spec/integration/prepared_statement_test.rb +130 -0
- data/spec/integration/schema_test.rb +183 -0
- data/spec/integration/spec_helper.rb +75 -0
- data/spec/integration/type_test.rb +96 -0
- data/spec/model/association_reflection_spec.rb +93 -0
- data/spec/model/associations_spec.rb +1780 -0
- data/spec/model/base_spec.rb +494 -0
- data/spec/model/caching_spec.rb +217 -0
- data/spec/model/dataset_methods_spec.rb +78 -0
- data/spec/model/eager_loading_spec.rb +1165 -0
- data/spec/model/hooks_spec.rb +472 -0
- data/spec/model/inflector_spec.rb +126 -0
- data/spec/model/model_spec.rb +588 -0
- data/spec/model/plugins_spec.rb +142 -0
- data/spec/model/record_spec.rb +1243 -0
- data/spec/model/schema_spec.rb +92 -0
- data/spec/model/spec_helper.rb +124 -0
- data/spec/model/validations_spec.rb +1080 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec.opts +0 -0
- data/spec/spec_config.rb.example +10 -0
- 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
|