outback 0.0.14 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +6 -0
  3. data/LICENSE +21 -0
  4. data/README.md +29 -3
  5. data/lib/outback.rb +16 -11
  6. data/lib/outback/archive.rb +6 -17
  7. data/lib/outback/backup.rb +24 -19
  8. data/lib/outback/cli.rb +6 -2
  9. data/lib/outback/configuration.rb +15 -10
  10. data/lib/outback/directory_source.rb +8 -7
  11. data/lib/outback/directory_target.rb +18 -11
  12. data/lib/outback/encryption_processor.rb +34 -0
  13. data/lib/outback/errors.rb +7 -0
  14. data/lib/outback/logging.rb +7 -0
  15. data/lib/outback/mysql_source.rb +9 -9
  16. data/lib/outback/processor.rb +17 -0
  17. data/lib/outback/s3_target.rb +18 -9
  18. data/lib/outback/sftp_target.rb +70 -0
  19. data/lib/outback/source.rb +9 -2
  20. data/lib/outback/source_archive.rb +17 -0
  21. data/lib/outback/support/attr_setter.rb +1 -1
  22. data/lib/outback/support/configurable.rb +5 -3
  23. data/lib/outback/target.rb +51 -14
  24. data/lib/outback/target_archive.rb +30 -0
  25. data/lib/outback/version.rb +3 -0
  26. data/lib/vendor/enumerable_ext.rb +9 -0
  27. data/lib/{outback/vendor → vendor}/metaclass.rb +1 -1
  28. data/lib/{outback/vendor → vendor}/methodphitamine.rb +1 -1
  29. data/lib/vendor/mysql.rb +1093 -0
  30. data/lib/vendor/mysql/charset.rb +325 -0
  31. data/lib/vendor/mysql/constants.rb +165 -0
  32. data/lib/vendor/mysql/error.rb +989 -0
  33. data/lib/vendor/mysql/packet.rb +78 -0
  34. data/lib/vendor/mysql/protocol.rb +770 -0
  35. data/lib/vendor/numeric_ext.rb +49 -0
  36. data/lib/vendor/string_ext.rb +19 -0
  37. metadata +53 -39
  38. data/MIT-LICENSE +0 -20
  39. data/VERSION +0 -1
  40. data/lib/outback/configuration_error.rb +0 -4
  41. data/lib/outback/directory_archive.rb +0 -8
  42. data/lib/outback/local_archive.rb +0 -6
  43. data/lib/outback/s3_archive.rb +0 -18
  44. data/lib/outback/temp_archive.rb +0 -5
  45. data/lib/outback/vendor/mysql.rb +0 -1214
@@ -0,0 +1,7 @@
1
+ module Outback
2
+ class Error < StandardError; end
3
+
4
+ class ConfigurationError < Error; end
5
+ class BackupError < Error; end
6
+ class ProcessingError < Error; end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Outback
2
+ module Logging
3
+ def logger
4
+ Outback
5
+ end
6
+ end
7
+ end
@@ -7,7 +7,7 @@ module Outback
7
7
  end
8
8
 
9
9
  def excludes
10
- @excludes ||= []
10
+ @excludes ||= ['mysql', 'performance_schema', 'information_schema']
11
11
  end
12
12
 
13
13
  def database(*names)
@@ -22,13 +22,13 @@ module Outback
22
22
  user && password
23
23
  end
24
24
 
25
- def create_archives(backup_name, timestamp, tmpdir)
25
+ def create_archives(timestamp, tmpdir)
26
26
  mysql_host = host || 'localhost'
27
27
  mysql_port = (port || 3306) unless socket
28
28
  if databases.empty?
29
- # (host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=nil)
29
+ # (host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=nil)
30
30
  mysql = Mysql.connect(mysql_host, user, password, nil, mysql_port, socket)
31
- @databases = mysql.databases - excludes
31
+ @databases = mysql.list_dbs - excludes
32
32
  mysql.close
33
33
  end
34
34
 
@@ -37,13 +37,13 @@ module Outback
37
37
  mysql_conf_file = Pathname.new(tmpdir).join('outback_my.cnf')
38
38
  File.open(mysql_conf_file, 'w') { |f| f << "[client]\npassword=#{password}\n" }
39
39
  FileUtils.chmod 0600, mysql_conf_file
40
- Outback.debug "MysqlSource: dumping database '#{database}'"
40
+ logger.debug "MysqlSource: dumping database '#{database}'"
41
41
  commandline = "mysqldump --defaults-extra-file=#{mysql_conf_file} --opt --user=#{user} --host=#{mysql_host} --port=#{mysql_port} #{database} | gzip > #{archive_name}"
42
42
  result = `#{commandline}`.strip
43
- Outback.debug(result) unless result.blank?
44
- Outback.info "Archived database #{database}"
45
- TempArchive.new(archive_name, self).tap { |archive| Outback.debug "dumped #{archive.filename.basename} with #{archive.size} bytes" }
43
+ logger.debug(result) unless result.blank?
44
+ logger.info "Archived database #{database}"
45
+ SourceArchive.new(archive_name).tap { |archive| logger.debug "dumped #{archive.filename.basename} with #{archive.size} bytes" }
46
46
  end
47
47
  end
48
48
  end
49
- end
49
+ end
@@ -0,0 +1,17 @@
1
+ module Outback
2
+ class Processor
3
+ include Configurable
4
+ include Logging
5
+
6
+ attr_reader :backup_name
7
+
8
+ def initialize(backup_name)
9
+ @backup_name = backup_name
10
+ end
11
+
12
+ def process!(archives)
13
+ archives.map { |archive| process_archive!(archive) }.flatten.compact
14
+ end
15
+
16
+ end
17
+ end
@@ -2,7 +2,7 @@ module Outback
2
2
  class S3Target < Target
3
3
  attr_setter :bucket_name, :access_key, :secret_key, :ttl, :prefix
4
4
 
5
- def display_name
5
+ def to_s
6
6
  "s3:#{bucket_name}/#{prefix}"
7
7
  end
8
8
 
@@ -12,7 +12,7 @@ module Outback
12
12
 
13
13
  def service(force_reconnect = false)
14
14
  @service = nil if force_reconnect
15
- @service ||= S3::Service.new(:access_key_id => access_key, :secret_access_key => secret_key)
15
+ @service ||= S3::Service.new(access_key_id: access_key, secret_access_key: secret_key, use_ssl: true)
16
16
  end
17
17
 
18
18
  def bucket
@@ -23,7 +23,7 @@ module Outback
23
23
  size = count = 0
24
24
  archives.each do |archive|
25
25
  object_name = [prefix.to_s, archive.filename.basename.to_s].join('/')
26
- Outback.debug "S3Target: storing #{archive.filename} in s3://#{bucket_name}/#{object_name}"
26
+ logger.debug "S3Target: storing #{archive.filename} in s3://#{bucket_name}/#{object_name}"
27
27
  object = bucket.objects.build(object_name)
28
28
  object.content = archive.open
29
29
  object.acl = :private
@@ -32,17 +32,26 @@ module Outback
32
32
  size += archive.size
33
33
  count += 1
34
34
  else
35
- Outback.error "S3 archive upload failed: #{object_name}"
35
+ logger.error "S3 archive upload failed: #{object_name}"
36
36
  end
37
37
  end
38
- Outback.info "Uploaded #{count} archives (#{size} bytes) to #{display_name}"
38
+ logger.info "Uploaded #{count} archives (#{size} bytes) to #{self}"
39
39
  count
40
40
  end
41
41
 
42
- def list_archives(name)
42
+ private
43
+
44
+ def list_all_archives
43
45
  entries = bucket.objects.select { |e| e.key.start_with?(prefix.to_s) && e.key[prefix.to_s.size..-1].match(Archive::NAME_PATTERN) }
44
- entries.map { |e| S3Archive.new(e.key, self) }.select(&its.backup_name == name)
46
+ entries.map { |e| build_archive(e.key, e.size) }
45
47
  end
48
+
49
+ def unlink_archive!(archive)
50
+ logger.debug "purging S3Archive: #{archive.filename}"
51
+ object = bucket.objects.find(archive.filename.to_s)
52
+ return true if object && object.destroy
53
+ raise BackupError, "could not find object #{archive.filename} for purging"
54
+ end
55
+
46
56
  end
47
-
48
- end
57
+ end
@@ -0,0 +1,70 @@
1
+ require 'net/sftp'
2
+
3
+ module Outback
4
+ class SftpTarget < Target
5
+
6
+ attr_reader :host
7
+ attr_setter :user, :password, :port, :path, :ttl, :ssh_opts
8
+
9
+ def initialize(backup_name, host)
10
+ super(backup_name)
11
+ @host = host
12
+ end
13
+
14
+ def valid?
15
+ host.present?
16
+ end
17
+
18
+ def to_s
19
+ "sftp:#{user}@#{host}#{':' + port.to_s if port}:#{path}"
20
+ end
21
+
22
+ def put(archives)
23
+ size = count = 0
24
+ connect do
25
+ archives.each do |archive|
26
+ basename = archive.filename.basename.to_s
27
+ upload_filename = path ? File.join(path, basename) : basename
28
+ logger.debug "SftpTarget: storing #{archive.filename} in sftp://#{user}@#{host}#{':' + port.to_s if port}:#{upload_filename}"
29
+ connection.upload!(archive.filename.to_s, upload_filename)
30
+ size += archive.size
31
+ count += 1
32
+ end
33
+ end
34
+ logger.info "Uploaded #{count} archives (#{size} bytes) to #{self}"
35
+ count
36
+ end
37
+
38
+ private
39
+
40
+ def connect
41
+ result = nil
42
+ Net::SFTP.start(host, user, ssh_options) do |sftp|
43
+ @connection = sftp
44
+ result = yield
45
+ end
46
+ result
47
+ ensure
48
+ @connection = nil
49
+ end
50
+
51
+ def ssh_options
52
+ options = ssh_opts || {}
53
+ if password
54
+ options[:password] = password
55
+ options[:auth_methods] ||= %w[password]
56
+ end
57
+ options[:port] = port if port
58
+ options
59
+ end
60
+
61
+ def list_all_archives
62
+ connection.dir.entries(path).select(&:file?).map { |entry| build_archive(File.join(path, entry.name), entry.attributes.size.to_i) }
63
+ end
64
+
65
+ def unlink_archive!(archive)
66
+ connection.remove!(archive.filename)
67
+ end
68
+
69
+ end
70
+ end
@@ -1,10 +1,17 @@
1
1
  module Outback
2
2
  class Source
3
3
  include Configurable
4
+ include Logging
5
+
6
+ attr_reader :backup_name
7
+
8
+ def initialize(backup_name)
9
+ @backup_name = backup_name
10
+ end
4
11
 
5
12
  def create_archive(backup_name, timestamp, tmpdir)
6
- # implement in subclasses
13
+ raise NotImplementedError
7
14
  end
8
15
 
9
16
  end
10
- end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Outback
2
+ class SourceArchive < Archive
3
+
4
+ def initialize(filename)
5
+ super(filename)
6
+ @size = @filename.size
7
+ end
8
+
9
+ def open
10
+ filename.open
11
+ end
12
+
13
+ def unlink
14
+ filename.unlink
15
+ end
16
+ end
17
+ end
@@ -21,4 +21,4 @@ module Outback
21
21
  end
22
22
  end
23
23
 
24
- end
24
+ end
@@ -3,11 +3,13 @@ module Outback
3
3
 
4
4
  def self.included(base)
5
5
  base.extend ClassMethods
6
- base.class_attribute :attributes
7
- base.attributes = []
8
6
  end
9
7
 
10
8
  module ClassMethods
9
+ def attributes
10
+ @attributes ||= []
11
+ end
12
+
11
13
  def configure(*args, &block)
12
14
  returning new(*args) do |instance|
13
15
  if block_given?
@@ -28,4 +30,4 @@ module Outback
28
30
  end
29
31
 
30
32
  end
31
- end
33
+ end
@@ -1,22 +1,59 @@
1
1
  module Outback
2
2
  class Target
3
3
  include Configurable
4
-
5
- def outdated_archives(name)
6
- list_archives(name).select(&:outdated?)
7
- end
8
-
9
- def purge!(name)
10
- size = count = 0
11
- outdated_archives(name).each do |archive|
12
- archive_size = archive.size
13
- if archive.purge!
14
- count += 1
15
- size += archive_size
4
+ include Logging
5
+
6
+ attr_reader :backup_name
7
+
8
+ def initialize(backup_name)
9
+ @backup_name = backup_name
10
+ end
11
+
12
+ def purge!
13
+ purged_archives = connect { purge_archives }
14
+ logger.info "Purged #{purged_archives.size} archives (#{purged_archives.sum(&:size)} bytes) from #{self}"
15
+ end
16
+
17
+ private
18
+
19
+ def connection
20
+ @connection
21
+ end
22
+
23
+ def connect
24
+ yield
25
+ end
26
+
27
+ def build_archive(filename, size)
28
+ TargetArchive.new(filename, size, self)
29
+ end
30
+
31
+ def list_all_archives
32
+ raise NotImplemented
33
+ end
34
+
35
+ def archives
36
+ list_all_archives.select(&it.match?(backup_name))
37
+ end
38
+
39
+ def purge_archives
40
+ archives.select do |archive|
41
+ if archive.outdated?
42
+ begin
43
+ logger.debug "purging archive: #{archive}"
44
+ unlink_archive!(archive)
45
+ true
46
+ rescue => e
47
+ logger.error "could not unlink archive #{archive}: #{e} #{e.message}"
48
+ false
49
+ end
16
50
  end
17
51
  end
18
- Outback.info "Purged #{count} archives (#{size} bytes) from #{display_name}"
52
+ end
53
+
54
+ def unlink_archive!(archive)
55
+ raise NotImplemented
19
56
  end
20
57
 
21
58
  end
22
- end
59
+ end
@@ -0,0 +1,30 @@
1
+ module Outback
2
+ class TargetArchive < Archive
3
+ attr_reader :target
4
+
5
+ def initialize(filename, size, target)
6
+ super(filename)
7
+ @size = size
8
+ @target = target
9
+ end
10
+
11
+ def match?(name)
12
+ name == backup_name
13
+ end
14
+
15
+ def ttl
16
+ target && target.ttl
17
+ end
18
+
19
+ def outdated?
20
+ if timestamp && ttl
21
+ Time.now - Time.strptime(timestamp, Outback::TIME_FORMAT) > ttl
22
+ end
23
+ end
24
+
25
+ def to_s
26
+ "#{target}: #{filename}"
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module Outback
2
+ VERSION = '1.0.2'
3
+ end
@@ -0,0 +1,9 @@
1
+ module Enumerable
2
+ def sum(identity = 0, &block)
3
+ if block_given?
4
+ map(&block).sum(identity)
5
+ else
6
+ inject { |sum, element| sum + element } || identity
7
+ end
8
+ end
9
+ end
@@ -12,4 +12,4 @@ class Object
12
12
  def class_def name, &blk
13
13
  class_eval { define_method name, &blk }
14
14
  end
15
- end
15
+ end
@@ -22,4 +22,4 @@ class It < BasicObject
22
22
  @methods.inject(obj) { |current, (args,block)| current.send(*args, &block) }
23
23
  end
24
24
  end
25
- end
25
+ end
@@ -0,0 +1,1093 @@
1
+ class Mysql
2
+
3
+ require_relative "mysql/constants"
4
+ require_relative "mysql/error"
5
+ require_relative "mysql/charset"
6
+ require_relative "mysql/protocol"
7
+ require_relative "mysql/packet.rb"
8
+ begin
9
+ require_relative "mysql/ext.so"
10
+ rescue LoadError
11
+ end
12
+
13
+ VERSION = 20913 # Version number of this library
14
+ MYSQL_UNIX_PORT = "/tmp/mysql.sock" # UNIX domain socket filename
15
+ MYSQL_TCP_PORT = 3306 # TCP socket port number
16
+
17
+ # @return [Mysql::Charset] character set of MySQL connection
18
+ attr_reader :charset
19
+ # @private
20
+ attr_reader :protocol
21
+
22
+ # @return [Boolean] if true, {#query} return {Mysql::Result}.
23
+ attr_accessor :query_with_result
24
+
25
+ class << self
26
+ # Make Mysql object without connecting.
27
+ # @return [Mysql]
28
+ def init
29
+ my = self.allocate
30
+ my.instance_eval{initialize}
31
+ my
32
+ end
33
+
34
+ # Make Mysql object and connect to mysqld.
35
+ # @param args same as arguments for {#connect}.
36
+ # @return [Mysql]
37
+ def new(*args)
38
+ my = self.init
39
+ my.connect(*args)
40
+ end
41
+
42
+ alias real_connect new
43
+ alias connect new
44
+
45
+ # Escape special character in string.
46
+ # @param [String] str
47
+ # @return [String]
48
+ def escape_string(str)
49
+ str.gsub(/[\0\n\r\\\'\"\x1a]/) do |s|
50
+ case s
51
+ when "\0" then "\\0"
52
+ when "\n" then "\\n"
53
+ when "\r" then "\\r"
54
+ when "\x1a" then "\\Z"
55
+ else "\\#{s}"
56
+ end
57
+ end
58
+ end
59
+ alias quote escape_string
60
+
61
+ # @return [String] client version. This value is dummy for MySQL/Ruby compatibility.
62
+ def client_info
63
+ "5.0.0"
64
+ end
65
+ alias get_client_info client_info
66
+
67
+ # @return [Integer] client version. This value is dummy for MySQL/Ruby compatibility.
68
+ def client_version
69
+ 50000
70
+ end
71
+ alias get_client_version client_version
72
+ end
73
+
74
+ def initialize
75
+ @fields = nil
76
+ @protocol = nil
77
+ @charset = nil
78
+ @connect_timeout = nil
79
+ @read_timeout = nil
80
+ @write_timeout = nil
81
+ @init_command = nil
82
+ @sqlstate = "00000"
83
+ @query_with_result = true
84
+ @host_info = nil
85
+ @last_error = nil
86
+ @result_exist = false
87
+ @local_infile = nil
88
+ end
89
+
90
+ # Connect to mysqld.
91
+ # @param [String / nil] host hostname mysqld running
92
+ # @param [String / nil] user username to connect to mysqld
93
+ # @param [String / nil] passwd password to connect to mysqld
94
+ # @param [String / nil] db initial database name
95
+ # @param [Integer / nil] port port number (used if host is not 'localhost' or nil)
96
+ # @param [String / nil] socket socket file name (used if host is 'localhost' or nil)
97
+ # @param [Integer / nil] flag connection flag. Mysql::CLIENT_* ORed
98
+ # @return self
99
+ def connect(host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=0)
100
+ if flag & CLIENT_COMPRESS != 0
101
+ warn 'unsupported flag: CLIENT_COMPRESS' if $VERBOSE
102
+ flag &= ~CLIENT_COMPRESS
103
+ end
104
+ @protocol = Protocol.new host, port, socket, @connect_timeout, @read_timeout, @write_timeout
105
+ @protocol.authenticate user, passwd, db, (@local_infile ? CLIENT_LOCAL_FILES : 0) | flag, @charset
106
+ @charset ||= @protocol.charset
107
+ @host_info = (host.nil? || host == "localhost") ? 'Localhost via UNIX socket' : "#{host} via TCP/IP"
108
+ query @init_command if @init_command
109
+ return self
110
+ end
111
+ alias real_connect connect
112
+
113
+ # Disconnect from mysql.
114
+ # @return [Mysql] self
115
+ def close
116
+ if @protocol
117
+ @protocol.quit_command
118
+ @protocol = nil
119
+ end
120
+ return self
121
+ end
122
+
123
+ # Disconnect from mysql without QUIT packet.
124
+ # @return [Mysql] self
125
+ def close!
126
+ if @protocol
127
+ @protocol.close
128
+ @protocol = nil
129
+ end
130
+ return self
131
+ end
132
+
133
+ # Set option for connection.
134
+ #
135
+ # Available options:
136
+ # Mysql::INIT_COMMAND, Mysql::OPT_CONNECT_TIMEOUT, Mysql::OPT_READ_TIMEOUT,
137
+ # Mysql::OPT_WRITE_TIMEOUT, Mysql::SET_CHARSET_NAME
138
+ # @param [Integer] opt option
139
+ # @param [Integer] value option value that is depend on opt
140
+ # @return [Mysql] self
141
+ def options(opt, value=nil)
142
+ case opt
143
+ when Mysql::INIT_COMMAND
144
+ @init_command = value.to_s
145
+ # when Mysql::OPT_COMPRESS
146
+ when Mysql::OPT_CONNECT_TIMEOUT
147
+ @connect_timeout = value
148
+ # when Mysql::GUESS_CONNECTION
149
+ when Mysql::OPT_LOCAL_INFILE
150
+ @local_infile = value
151
+ # when Mysql::OPT_NAMED_PIPE
152
+ # when Mysql::OPT_PROTOCOL
153
+ when Mysql::OPT_READ_TIMEOUT
154
+ @read_timeout = value.to_i
155
+ # when Mysql::OPT_RECONNECT
156
+ # when Mysql::SET_CLIENT_IP
157
+ # when Mysql::OPT_SSL_VERIFY_SERVER_CERT
158
+ # when Mysql::OPT_USE_EMBEDDED_CONNECTION
159
+ # when Mysql::OPT_USE_REMOTE_CONNECTION
160
+ when Mysql::OPT_WRITE_TIMEOUT
161
+ @write_timeout = value.to_i
162
+ # when Mysql::READ_DEFAULT_FILE
163
+ # when Mysql::READ_DEFAULT_GROUP
164
+ # when Mysql::REPORT_DATA_TRUNCATION
165
+ # when Mysql::SECURE_AUTH
166
+ # when Mysql::SET_CHARSET_DIR
167
+ when Mysql::SET_CHARSET_NAME
168
+ @charset = Charset.by_name value.to_s
169
+ # when Mysql::SHARED_MEMORY_BASE_NAME
170
+ else
171
+ warn "option not implemented: #{opt}" if $VERBOSE
172
+ end
173
+ self
174
+ end
175
+
176
+ # Escape special character in MySQL.
177
+ #
178
+ # In Ruby 1.8, this is not safe for multibyte charset such as 'SJIS'.
179
+ # You should use place-holder in prepared-statement.
180
+ # @param [String] str
181
+ # return [String]
182
+ def escape_string(str)
183
+ if not defined? Encoding and @charset.unsafe
184
+ raise ClientError, 'Mysql#escape_string is called for unsafe multibyte charset'
185
+ end
186
+ self.class.escape_string str
187
+ end
188
+ alias quote escape_string
189
+
190
+ # @return [String] client version
191
+ def client_info
192
+ self.class.client_info
193
+ end
194
+ alias get_client_info client_info
195
+
196
+ # @return [Integer] client version
197
+ def client_version
198
+ self.class.client_version
199
+ end
200
+ alias get_client_version client_version
201
+
202
+ # Set charset of MySQL connection.
203
+ # @param [String / Mysql::Charset] cs
204
+ def charset=(cs)
205
+ charset = cs.is_a?(Charset) ? cs : Charset.by_name(cs)
206
+ if @protocol
207
+ @protocol.charset = charset
208
+ query "SET NAMES #{charset.name}"
209
+ end
210
+ @charset = charset
211
+ cs
212
+ end
213
+
214
+ # @return [String] charset name
215
+ def character_set_name
216
+ @charset.name
217
+ end
218
+
219
+ # @return [Integer] last error number
220
+ def errno
221
+ @last_error ? @last_error.errno : 0
222
+ end
223
+
224
+ # @return [String] last error message
225
+ def error
226
+ @last_error && @last_error.error
227
+ end
228
+
229
+ # @return [String] sqlstate for last error
230
+ def sqlstate
231
+ @last_error ? @last_error.sqlstate : "00000"
232
+ end
233
+
234
+ # @return [Integer] number of columns for last query
235
+ def field_count
236
+ @fields.size
237
+ end
238
+
239
+ # @return [String] connection type
240
+ def host_info
241
+ @host_info
242
+ end
243
+ alias get_host_info host_info
244
+
245
+ # @return [Integer] protocol version
246
+ def proto_info
247
+ Mysql::Protocol::VERSION
248
+ end
249
+ alias get_proto_info proto_info
250
+
251
+ # @return [String] server version
252
+ def server_info
253
+ check_connection
254
+ @protocol.server_info
255
+ end
256
+ alias get_server_info server_info
257
+
258
+ # @return [Integer] server version
259
+ def server_version
260
+ check_connection
261
+ @protocol.server_version
262
+ end
263
+ alias get_server_version server_version
264
+
265
+ # @return [String] information for last query
266
+ def info
267
+ @protocol && @protocol.message
268
+ end
269
+
270
+ # @return [Integer] number of affected records by insert/update/delete.
271
+ def affected_rows
272
+ @protocol ? @protocol.affected_rows : 0
273
+ end
274
+
275
+ # @return [Integer] latest auto_increment value
276
+ def insert_id
277
+ @protocol ? @protocol.insert_id : 0
278
+ end
279
+
280
+ # @return [Integer] number of warnings for previous query
281
+ def warning_count
282
+ @protocol ? @protocol.warning_count : 0
283
+ end
284
+
285
+ # Kill query.
286
+ # @param [Integer] pid thread id
287
+ # @return [Mysql] self
288
+ def kill(pid)
289
+ check_connection
290
+ @protocol.kill_command pid
291
+ self
292
+ end
293
+
294
+ # database list.
295
+ # @param [String] db database name that may contain wild card.
296
+ # @return [Array<String>] database list
297
+ def list_dbs(db=nil)
298
+ db &&= db.gsub(/[\\\']/){"\\#{$&}"}
299
+ query(db ? "show databases like '#{db}'" : "show databases").map(&:first)
300
+ end
301
+
302
+ # Execute query string.
303
+ # @param [String] str Query.
304
+ # @yield [Mysql::Result] evaluated per query.
305
+ # @return [Mysql::Result] If {#query_with_result} is true and result set exist.
306
+ # @return [nil] If {#query_with_result} is true and the query does not return result set.
307
+ # @return [Mysql] If {#query_with_result} is false or block is specified
308
+ # @example
309
+ # my.query("select 1,NULL,'abc'").fetch # => [1, nil, "abc"]
310
+ def query(str, &block)
311
+ check_connection
312
+ @fields = nil
313
+ begin
314
+ nfields = @protocol.query_command str
315
+ if nfields
316
+ @fields = @protocol.retr_fields nfields
317
+ @result_exist = true
318
+ end
319
+ if block
320
+ while true
321
+ block.call store_result if @fields
322
+ break unless next_result
323
+ end
324
+ return self
325
+ end
326
+ if @query_with_result
327
+ return @fields ? store_result : nil
328
+ else
329
+ return self
330
+ end
331
+ rescue ServerError => e
332
+ @last_error = e
333
+ @sqlstate = e.sqlstate
334
+ raise
335
+ end
336
+ end
337
+ alias real_query query
338
+
339
+ # Get all data for last query if query_with_result is false.
340
+ # @return [Mysql::Result]
341
+ def store_result
342
+ check_connection
343
+ raise ClientError, 'invalid usage' unless @result_exist
344
+ res = Result.new @fields, @protocol
345
+ @result_exist = false
346
+ res
347
+ end
348
+
349
+ # @return [Integer] Thread ID
350
+ def thread_id
351
+ check_connection
352
+ @protocol.thread_id
353
+ end
354
+
355
+ # Use result of query. The result data is retrieved when you use Mysql::Result#fetch.
356
+ # @return [Mysql::Result]
357
+ def use_result
358
+ store_result
359
+ end
360
+
361
+ # Set server option.
362
+ # @param [Integer] opt {Mysql::OPTION_MULTI_STATEMENTS_ON} or {Mysql::OPTION_MULTI_STATEMENTS_OFF}
363
+ # @return [Mysql] self
364
+ def set_server_option(opt)
365
+ check_connection
366
+ @protocol.set_option_command opt
367
+ self
368
+ end
369
+
370
+ # @return [Boolean] true if multiple queries are specified and unexecuted queries exists.
371
+ def more_results
372
+ @protocol.server_status & SERVER_MORE_RESULTS_EXISTS != 0
373
+ end
374
+ alias more_results? more_results
375
+
376
+ # execute next query if multiple queries are specified.
377
+ # @return [Boolean] true if next query exists.
378
+ def next_result
379
+ return false unless more_results
380
+ check_connection
381
+ @fields = nil
382
+ nfields = @protocol.get_result
383
+ if nfields
384
+ @fields = @protocol.retr_fields nfields
385
+ @result_exist = true
386
+ end
387
+ return true
388
+ end
389
+
390
+ # Parse prepared-statement.
391
+ # @param [String] str query string
392
+ # @return [Mysql::Stmt] Prepared-statement object
393
+ def prepare(str)
394
+ st = Stmt.new @protocol, @charset
395
+ st.prepare str
396
+ st
397
+ end
398
+
399
+ # @private
400
+ # Make empty prepared-statement object.
401
+ # @return [Mysql::Stmt] If block is not specified.
402
+ def stmt_init
403
+ Stmt.new @protocol, @charset
404
+ end
405
+
406
+ # Returns Mysql::Result object that is empty.
407
+ # Use fetch_fields to get list of fields.
408
+ # @param [String] table table name.
409
+ # @param [String] field field name that may contain wild card.
410
+ # @return [Mysql::Result]
411
+ def list_fields(table, field=nil)
412
+ check_connection
413
+ begin
414
+ fields = @protocol.field_list_command table, field
415
+ return Result.new fields
416
+ rescue ServerError => e
417
+ @last_error = e
418
+ @sqlstate = e.sqlstate
419
+ raise
420
+ end
421
+ end
422
+
423
+ # @return [Mysql::Result] containing process list
424
+ def list_processes
425
+ check_connection
426
+ @fields = @protocol.process_info_command
427
+ @result_exist = true
428
+ store_result
429
+ end
430
+
431
+ # @note for Ruby 1.8: This is not multi-byte safe. Don't use for multi-byte charset such as cp932.
432
+ # @param [String] table database name that may contain wild card.
433
+ # @return [Array<String>] list of table name.
434
+ def list_tables(table=nil)
435
+ q = table ? "show tables like '#{quote table}'" : "show tables"
436
+ query(q).map(&:first)
437
+ end
438
+
439
+ # Check whether the connection is available.
440
+ # @return [Mysql] self
441
+ def ping
442
+ check_connection
443
+ @protocol.ping_command
444
+ self
445
+ end
446
+
447
+ # Flush tables or caches.
448
+ # @param [Integer] op operation. Use Mysql::REFRESH_* value.
449
+ # @return [Mysql] self
450
+ def refresh(op)
451
+ check_connection
452
+ @protocol.refresh_command op
453
+ self
454
+ end
455
+
456
+ # Reload grant tables.
457
+ # @return [Mysql] self
458
+ def reload
459
+ refresh Mysql::REFRESH_GRANT
460
+ end
461
+
462
+ # Select default database
463
+ # @return [Mysql] self
464
+ def select_db(db)
465
+ query "use #{db}"
466
+ self
467
+ end
468
+
469
+ # shutdown server.
470
+ # @return [Mysql] self
471
+ def shutdown(level=0)
472
+ check_connection
473
+ @protocol.shutdown_command level
474
+ self
475
+ end
476
+
477
+ # @return [String] statistics message
478
+ def stat
479
+ @protocol ? @protocol.statistics_command : 'MySQL server has gone away'
480
+ end
481
+
482
+ # Commit transaction
483
+ # @return [Mysql] self
484
+ def commit
485
+ query 'commit'
486
+ self
487
+ end
488
+
489
+ # Rollback transaction
490
+ # @return [Mysql] self
491
+ def rollback
492
+ query 'rollback'
493
+ self
494
+ end
495
+
496
+ # Set autocommit mode
497
+ # @param [Boolean] flag
498
+ # @return [Mysql] self
499
+ def autocommit(flag)
500
+ query "set autocommit=#{flag ? 1 : 0}"
501
+ self
502
+ end
503
+
504
+ private
505
+
506
+ def check_connection
507
+ raise ClientError::ServerGoneError, 'MySQL server has gone away' unless @protocol
508
+ end
509
+
510
+ # @!visibility public
511
+ # Field class
512
+ class Field
513
+ # @return [String] database name
514
+ attr_reader :db
515
+ # @return [String] table name
516
+ attr_reader :table
517
+ # @return [String] original table name
518
+ attr_reader :org_table
519
+ # @return [String] field name
520
+ attr_reader :name
521
+ # @return [String] original field name
522
+ attr_reader :org_name
523
+ # @return [Integer] charset id number
524
+ attr_reader :charsetnr
525
+ # @return [Integer] field length
526
+ attr_reader :length
527
+ # @return [Integer] field type
528
+ attr_reader :type
529
+ # @return [Integer] flag
530
+ attr_reader :flags
531
+ # @return [Integer] number of decimals
532
+ attr_reader :decimals
533
+ # @return [String] defualt value
534
+ attr_reader :default
535
+ alias :def :default
536
+
537
+ # @private
538
+ attr_accessor :result
539
+
540
+ # @attr [Protocol::FieldPacket] packet
541
+ def initialize(packet)
542
+ @db, @table, @org_table, @name, @org_name, @charsetnr, @length, @type, @flags, @decimals, @default =
543
+ packet.db, packet.table, packet.org_table, packet.name, packet.org_name, packet.charsetnr, packet.length, packet.type, packet.flags, packet.decimals, packet.default
544
+ @flags |= NUM_FLAG if is_num_type?
545
+ @max_length = nil
546
+ end
547
+
548
+ # @return [Hash] field information
549
+ def hash
550
+ {
551
+ "name" => @name,
552
+ "table" => @table,
553
+ "def" => @default,
554
+ "type" => @type,
555
+ "length" => @length,
556
+ "max_length" => max_length,
557
+ "flags" => @flags,
558
+ "decimals" => @decimals
559
+ }
560
+ end
561
+
562
+ # @private
563
+ def inspect
564
+ "#<Mysql::Field:#{@name}>"
565
+ end
566
+
567
+ # @return [Boolean] true if numeric field.
568
+ def is_num?
569
+ @flags & NUM_FLAG != 0
570
+ end
571
+
572
+ # @return [Boolean] true if not null field.
573
+ def is_not_null?
574
+ @flags & NOT_NULL_FLAG != 0
575
+ end
576
+
577
+ # @return [Boolean] true if primary key field.
578
+ def is_pri_key?
579
+ @flags & PRI_KEY_FLAG != 0
580
+ end
581
+
582
+ # @return [Integer] maximum width of the field for the result set
583
+ def max_length
584
+ return @max_length if @max_length
585
+ @max_length = 0
586
+ @result.calculate_field_max_length if @result
587
+ @max_length
588
+ end
589
+
590
+ attr_writer :max_length
591
+
592
+ private
593
+
594
+ def is_num_type?
595
+ [TYPE_DECIMAL, TYPE_TINY, TYPE_SHORT, TYPE_LONG, TYPE_FLOAT, TYPE_DOUBLE, TYPE_LONGLONG, TYPE_INT24].include?(@type) || (@type == TYPE_TIMESTAMP && (@length == 14 || @length == 8))
596
+ end
597
+
598
+ end
599
+
600
+ # @!visibility public
601
+ # Result set
602
+ class ResultBase
603
+ include Enumerable
604
+
605
+ # @return [Array<Mysql::Field>] field list
606
+ attr_reader :fields
607
+
608
+ # @param [Array of Mysql::Field] fields
609
+ def initialize(fields)
610
+ @fields = fields
611
+ @field_index = 0 # index of field
612
+ @records = [] # all records
613
+ @index = 0 # index of record
614
+ @fieldname_with_table = nil
615
+ @fetched_record = nil
616
+ end
617
+
618
+ # ignore
619
+ # @return [void]
620
+ def free
621
+ end
622
+
623
+ # @return [Integer] number of record
624
+ def size
625
+ @records.size
626
+ end
627
+ alias num_rows size
628
+
629
+ # @return [Array] current record data
630
+ def fetch
631
+ @fetched_record = nil
632
+ return nil if @index >= @records.size
633
+ @records[@index] = @records[@index].to_a unless @records[@index].is_a? Array
634
+ @fetched_record = @records[@index]
635
+ @index += 1
636
+ return @fetched_record
637
+ end
638
+ alias fetch_row fetch
639
+
640
+ # Return data of current record as Hash.
641
+ # The hash key is field name.
642
+ # @param [Boolean] with_table if true, hash key is "table_name.field_name".
643
+ # @return [Hash] current record data
644
+ def fetch_hash(with_table=nil)
645
+ row = fetch
646
+ return nil unless row
647
+ if with_table and @fieldname_with_table.nil?
648
+ @fieldname_with_table = @fields.map{|f| [f.table, f.name].join(".")}
649
+ end
650
+ ret = {}
651
+ @fields.each_index do |i|
652
+ fname = with_table ? @fieldname_with_table[i] : @fields[i].name
653
+ ret[fname] = row[i]
654
+ end
655
+ ret
656
+ end
657
+
658
+ # Iterate block with record.
659
+ # @yield [Array] record data
660
+ # @return [self] self. If block is not specified, this returns Enumerator.
661
+ def each(&block)
662
+ return enum_for(:each) unless block
663
+ while rec = fetch
664
+ block.call rec
665
+ end
666
+ self
667
+ end
668
+
669
+ # Iterate block with record as Hash.
670
+ # @param [Boolean] with_table if true, hash key is "table_name.field_name".
671
+ # @yield [Hash] record data
672
+ # @return [self] self. If block is not specified, this returns Enumerator.
673
+ def each_hash(with_table=nil, &block)
674
+ return enum_for(:each_hash, with_table) unless block
675
+ while rec = fetch_hash(with_table)
676
+ block.call rec
677
+ end
678
+ self
679
+ end
680
+
681
+ # Set record position
682
+ # @param [Integer] n record index
683
+ # @return [self] self
684
+ def data_seek(n)
685
+ @index = n
686
+ self
687
+ end
688
+
689
+ # @return [Integer] current record position
690
+ def row_tell
691
+ @index
692
+ end
693
+
694
+ # Set current position of record
695
+ # @param [Integer] n record index
696
+ # @return [Integer] previous position
697
+ def row_seek(n)
698
+ ret = @index
699
+ @index = n
700
+ ret
701
+ end
702
+ end
703
+
704
+ # @!visibility public
705
+ # Result set for simple query
706
+ class Result < ResultBase
707
+ # @private
708
+ # @param [Array<Mysql::Field>] fields
709
+ # @param [Mysql::Protocol] protocol
710
+ def initialize(fields, protocol=nil)
711
+ super fields
712
+ return unless protocol
713
+ @records = protocol.retr_all_records fields
714
+ fields.each{|f| f.result = self} # for calculating max_field
715
+ end
716
+
717
+ # @private
718
+ # calculate max_length of all fields
719
+ def calculate_field_max_length
720
+ max_length = Array.new(@fields.size, 0)
721
+ @records.each_with_index do |rec, i|
722
+ rec = @records[i] = rec.to_a if rec.is_a? RawRecord
723
+ max_length.each_index do |i|
724
+ max_length[i] = rec[i].length if rec[i] && rec[i].length > max_length[i]
725
+ end
726
+ end
727
+ max_length.each_with_index do |len, i|
728
+ @fields[i].max_length = len
729
+ end
730
+ end
731
+
732
+ # @return [Mysql::Field] current field
733
+ def fetch_field
734
+ return nil if @field_index >= @fields.length
735
+ ret = @fields[@field_index]
736
+ @field_index += 1
737
+ ret
738
+ end
739
+
740
+ # @return [Integer] current field position
741
+ def field_tell
742
+ @field_index
743
+ end
744
+
745
+ # Set field position
746
+ # @param [Integer] n field index
747
+ # @return [Integer] previous position
748
+ def field_seek(n)
749
+ ret = @field_index
750
+ @field_index = n
751
+ ret
752
+ end
753
+
754
+ # Return specified field
755
+ # @param [Integer] n field index
756
+ # @return [Mysql::Field] field
757
+ def fetch_field_direct(n)
758
+ raise ClientError, "invalid argument: #{n}" if n < 0 or n >= @fields.length
759
+ @fields[n]
760
+ end
761
+
762
+ # @return [Array<Mysql::Field>] all fields
763
+ def fetch_fields
764
+ @fields
765
+ end
766
+
767
+ # @return [Array<Integer>] length of each fields
768
+ def fetch_lengths
769
+ return nil unless @fetched_record
770
+ @fetched_record.map{|c|c.nil? ? 0 : c.length}
771
+ end
772
+
773
+ # @return [Integer] number of fields
774
+ def num_fields
775
+ @fields.size
776
+ end
777
+ end
778
+
779
+ # @!visibility private
780
+ # Result set for prepared statement
781
+ class StatementResult < ResultBase
782
+ # @private
783
+ # @param [Array<Mysql::Field>] fields
784
+ # @param [Mysql::Protocol] protocol
785
+ # @param [Mysql::Charset] charset
786
+ def initialize(fields, protocol, charset)
787
+ super fields
788
+ @records = protocol.stmt_retr_all_records @fields, charset
789
+ end
790
+ end
791
+
792
+ # @!visibility public
793
+ # Prepared statement
794
+ # @!attribute [r] affected_rows
795
+ # @return [Integer]
796
+ # @!attribute [r] insert_id
797
+ # @return [Integer]
798
+ # @!attribute [r] server_status
799
+ # @return [Integer]
800
+ # @!attribute [r] warning_count
801
+ # @return [Integer]
802
+ # @!attribute [r] param_count
803
+ # @return [Integer]
804
+ # @!attribute [r] fields
805
+ # @return [Array<Mysql::Field>]
806
+ # @!attribute [r] sqlstate
807
+ # @return [String]
808
+ class Stmt
809
+ include Enumerable
810
+
811
+ attr_reader :affected_rows, :insert_id, :server_status, :warning_count
812
+ attr_reader :param_count, :fields, :sqlstate
813
+
814
+ # @private
815
+ def self.finalizer(protocol, statement_id)
816
+ proc do
817
+ protocol.gc_stmt statement_id
818
+ end
819
+ end
820
+
821
+ # @private
822
+ # @param [Mysql::Protocol] protocol
823
+ # @param [Mysql::Charset] charset
824
+ def initialize(protocol, charset)
825
+ @protocol = protocol
826
+ @charset = charset
827
+ @statement_id = nil
828
+ @affected_rows = @insert_id = @server_status = @warning_count = 0
829
+ @sqlstate = "00000"
830
+ @param_count = nil
831
+ @bind_result = nil
832
+ end
833
+
834
+ # @private
835
+ # parse prepared-statement and return {Mysql::Stmt} object
836
+ # @param [String] str query string
837
+ # @return self
838
+ def prepare(str)
839
+ close
840
+ begin
841
+ @sqlstate = "00000"
842
+ @statement_id, @param_count, @fields = @protocol.stmt_prepare_command(str)
843
+ rescue ServerError => e
844
+ @last_error = e
845
+ @sqlstate = e.sqlstate
846
+ raise
847
+ end
848
+ ObjectSpace.define_finalizer(self, self.class.finalizer(@protocol, @statement_id))
849
+ self
850
+ end
851
+
852
+ # Execute prepared statement.
853
+ # @param [Object] values values passed to query
854
+ # @return [Mysql::Stmt] self
855
+ def execute(*values)
856
+ raise ClientError, "not prepared" unless @param_count
857
+ raise ClientError, "parameter count mismatch" if values.length != @param_count
858
+ values = values.map{|v| @charset.convert v}
859
+ begin
860
+ @sqlstate = "00000"
861
+ nfields = @protocol.stmt_execute_command @statement_id, values
862
+ if nfields
863
+ @fields = @protocol.retr_fields nfields
864
+ @result = StatementResult.new @fields, @protocol, @charset
865
+ else
866
+ @affected_rows, @insert_id, @server_status, @warning_count, @info =
867
+ @protocol.affected_rows, @protocol.insert_id, @protocol.server_status, @protocol.warning_count, @protocol.message
868
+ end
869
+ return self
870
+ rescue ServerError => e
871
+ @last_error = e
872
+ @sqlstate = e.sqlstate
873
+ raise
874
+ end
875
+ end
876
+
877
+ # Close prepared statement
878
+ # @return [void]
879
+ def close
880
+ ObjectSpace.undefine_finalizer(self)
881
+ @protocol.stmt_close_command @statement_id if @statement_id
882
+ @statement_id = nil
883
+ end
884
+
885
+ # @return [Array] current record data
886
+ def fetch
887
+ row = @result.fetch
888
+ return row unless @bind_result
889
+ row.zip(@bind_result).map do |col, type|
890
+ if col.nil?
891
+ nil
892
+ elsif [Numeric, Integer, Fixnum].include? type
893
+ col.to_i
894
+ elsif type == String
895
+ col.to_s
896
+ elsif type == Float && !col.is_a?(Float)
897
+ col.to_i.to_f
898
+ elsif type == Mysql::Time && !col.is_a?(Mysql::Time)
899
+ if col.to_s =~ /\A\d+\z/
900
+ i = col.to_s.to_i
901
+ if i < 100000000
902
+ y = i/10000
903
+ m = i/100%100
904
+ d = i%100
905
+ h, mm, s = 0
906
+ else
907
+ y = i/10000000000
908
+ m = i/100000000%100
909
+ d = i/1000000%100
910
+ h = i/10000%100
911
+ mm= i/100%100
912
+ s = i%100
913
+ end
914
+ if y < 70
915
+ y += 2000
916
+ elsif y < 100
917
+ y += 1900
918
+ end
919
+ Mysql::Time.new(y, m, d, h, mm, s)
920
+ else
921
+ Mysql::Time.new
922
+ end
923
+ else
924
+ col
925
+ end
926
+ end
927
+ end
928
+
929
+ # Return data of current record as Hash.
930
+ # The hash key is field name.
931
+ # @param [Boolean] with_table if true, hash key is "table_name.field_name".
932
+ # @return [Hash] record data
933
+ def fetch_hash(with_table=nil)
934
+ @result.fetch_hash with_table
935
+ end
936
+
937
+ # Set retrieve type of value
938
+ # @param [Numeric / Fixnum / Integer / Float / String / Mysql::Time / nil] args value type
939
+ # @return [Mysql::Stmt] self
940
+ def bind_result(*args)
941
+ if @fields.length != args.length
942
+ raise ClientError, "bind_result: result value count(#{@fields.length}) != number of argument(#{args.length})"
943
+ end
944
+ args.each do |a|
945
+ raise TypeError unless [Numeric, Fixnum, Integer, Float, String, Mysql::Time, nil].include? a
946
+ end
947
+ @bind_result = args
948
+ self
949
+ end
950
+
951
+ # Iterate block with record.
952
+ # @yield [Array] record data
953
+ # @return [Mysql::Stmt] self
954
+ # @return [Enumerator] If block is not specified
955
+ def each(&block)
956
+ return enum_for(:each) unless block
957
+ while rec = fetch
958
+ block.call rec
959
+ end
960
+ self
961
+ end
962
+
963
+ # Iterate block with record as Hash.
964
+ # @param [Boolean] with_table if true, hash key is "table_name.field_name".
965
+ # @yield [Hash] record data
966
+ # @return [Mysql::Stmt] self
967
+ # @return [Enumerator] If block is not specified
968
+ def each_hash(with_table=nil, &block)
969
+ return enum_for(:each_hash, with_table) unless block
970
+ while rec = fetch_hash(with_table)
971
+ block.call rec
972
+ end
973
+ self
974
+ end
975
+
976
+ # @return [Integer] number of record
977
+ def size
978
+ @result.size
979
+ end
980
+ alias num_rows size
981
+
982
+ # Set record position
983
+ # @param [Integer] n record index
984
+ # @return [void]
985
+ def data_seek(n)
986
+ @result.data_seek(n)
987
+ end
988
+
989
+ # @return [Integer] current record position
990
+ def row_tell
991
+ @result.row_tell
992
+ end
993
+
994
+ # Set current position of record
995
+ # @param [Integer] n record index
996
+ # @return [Integer] previous position
997
+ def row_seek(n)
998
+ @result.row_seek(n)
999
+ end
1000
+
1001
+ # @return [Integer] number of columns for last query
1002
+ def field_count
1003
+ @fields.length
1004
+ end
1005
+
1006
+ # ignore
1007
+ # @return [void]
1008
+ def free_result
1009
+ end
1010
+
1011
+ # Returns Mysql::Result object that is empty.
1012
+ # Use fetch_fields to get list of fields.
1013
+ # @return [Mysql::Result]
1014
+ def result_metadata
1015
+ return nil if @fields.empty?
1016
+ Result.new @fields
1017
+ end
1018
+ end
1019
+
1020
+ # @!visibility public
1021
+ # @!attribute [rw] year
1022
+ # @return [Integer]
1023
+ # @!attribute [rw] month
1024
+ # @return [Integer]
1025
+ # @!attribute [rw] day
1026
+ # @return [Integer]
1027
+ # @!attribute [rw] hour
1028
+ # @return [Integer]
1029
+ # @!attribute [rw] minute
1030
+ # @return [Integer]
1031
+ # @!attribute [rw] second
1032
+ # @return [Integer]
1033
+ # @!attribute [rw] neg
1034
+ # @return [Boolean] negative flag
1035
+ # @!attribute [rw] second_part
1036
+ # @return [Integer]
1037
+ class Time
1038
+ # @param [Integer] year
1039
+ # @param [Integer] month
1040
+ # @param [Integer] day
1041
+ # @param [Integer] hour
1042
+ # @param [Integer] minute
1043
+ # @param [Integer] second
1044
+ # @param [Boolean] neg negative flag
1045
+ # @param [Integer] second_part
1046
+ def initialize(year=0, month=0, day=0, hour=0, minute=0, second=0, neg=false, second_part=0)
1047
+ @date_flag = !(hour && minute && second)
1048
+ @year, @month, @day, @hour, @minute, @second, @neg, @second_part =
1049
+ year.to_i, month.to_i, day.to_i, hour.to_i, minute.to_i, second.to_i, neg, second_part.to_i
1050
+ end
1051
+ attr_accessor :year, :month, :day, :hour, :minute, :second, :neg, :second_part
1052
+ alias mon month
1053
+ alias min minute
1054
+ alias sec second
1055
+
1056
+ # @private
1057
+ def ==(other)
1058
+ other.is_a?(Mysql::Time) &&
1059
+ @year == other.year && @month == other.month && @day == other.day &&
1060
+ @hour == other.hour && @minute == other.minute && @second == other.second &&
1061
+ @neg == neg && @second_part == other.second_part
1062
+ end
1063
+
1064
+ # @private
1065
+ def eql?(other)
1066
+ self == other
1067
+ end
1068
+
1069
+ # @return [String] "yyyy-mm-dd HH:MM:SS"
1070
+ def to_s
1071
+ if @date_flag
1072
+ sprintf "%04d-%02d-%02d", year, mon, day
1073
+ elsif year == 0 and mon == 0 and day == 0
1074
+ h = neg ? hour * -1 : hour
1075
+ sprintf "%02d:%02d:%02d", h, min, sec
1076
+ else
1077
+ sprintf "%04d-%02d-%02d %02d:%02d:%02d", year, mon, day, hour, min, sec
1078
+ end
1079
+ end
1080
+
1081
+ # @return [Integer] yyyymmddHHMMSS
1082
+ def to_i
1083
+ sprintf("%04d%02d%02d%02d%02d%02d", year, mon, day, hour, min, sec).to_i
1084
+ end
1085
+
1086
+ # @private
1087
+ def inspect
1088
+ sprintf "#<#{self.class.name}:%04d-%02d-%02d %02d:%02d:%02d>", year, mon, day, hour, min, sec
1089
+ end
1090
+
1091
+ end
1092
+
1093
+ end