sbf-data_objects 0.10.17
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.
- checksums.yaml +7 -0
- data/ChangeLog.markdown +115 -0
- data/LICENSE +20 -0
- data/README.markdown +18 -0
- data/Rakefile +20 -0
- data/lib/data_objects/byte_array.rb +6 -0
- data/lib/data_objects/command.rb +79 -0
- data/lib/data_objects/connection.rb +95 -0
- data/lib/data_objects/error/connection_error.rb +4 -0
- data/lib/data_objects/error/data_error.rb +4 -0
- data/lib/data_objects/error/integrity_error.rb +4 -0
- data/lib/data_objects/error/sql_error.rb +17 -0
- data/lib/data_objects/error/syntax_error.rb +4 -0
- data/lib/data_objects/error/transaction_error.rb +4 -0
- data/lib/data_objects/error.rb +4 -0
- data/lib/data_objects/extension.rb +9 -0
- data/lib/data_objects/logger.rb +247 -0
- data/lib/data_objects/pooling.rb +243 -0
- data/lib/data_objects/quoting.rb +99 -0
- data/lib/data_objects/reader.rb +45 -0
- data/lib/data_objects/result.rb +21 -0
- data/lib/data_objects/spec/lib/pending_helpers.rb +13 -0
- data/lib/data_objects/spec/lib/ssl.rb +19 -0
- data/lib/data_objects/spec/setup.rb +5 -0
- data/lib/data_objects/spec/shared/command_spec.rb +201 -0
- data/lib/data_objects/spec/shared/connection_spec.rb +148 -0
- data/lib/data_objects/spec/shared/encoding_spec.rb +161 -0
- data/lib/data_objects/spec/shared/error/sql_error_spec.rb +23 -0
- data/lib/data_objects/spec/shared/quoting_spec.rb +0 -0
- data/lib/data_objects/spec/shared/reader_spec.rb +180 -0
- data/lib/data_objects/spec/shared/result_spec.rb +67 -0
- data/lib/data_objects/spec/shared/typecast/array_spec.rb +29 -0
- data/lib/data_objects/spec/shared/typecast/bigdecimal_spec.rb +112 -0
- data/lib/data_objects/spec/shared/typecast/boolean_spec.rb +133 -0
- data/lib/data_objects/spec/shared/typecast/byte_array_spec.rb +76 -0
- data/lib/data_objects/spec/shared/typecast/class_spec.rb +53 -0
- data/lib/data_objects/spec/shared/typecast/date_spec.rb +114 -0
- data/lib/data_objects/spec/shared/typecast/datetime_spec.rb +140 -0
- data/lib/data_objects/spec/shared/typecast/float_spec.rb +115 -0
- data/lib/data_objects/spec/shared/typecast/integer_spec.rb +92 -0
- data/lib/data_objects/spec/shared/typecast/ipaddr_spec.rb +0 -0
- data/lib/data_objects/spec/shared/typecast/nil_spec.rb +107 -0
- data/lib/data_objects/spec/shared/typecast/other_spec.rb +41 -0
- data/lib/data_objects/spec/shared/typecast/range_spec.rb +29 -0
- data/lib/data_objects/spec/shared/typecast/string_spec.rb +130 -0
- data/lib/data_objects/spec/shared/typecast/time_spec.rb +111 -0
- data/lib/data_objects/transaction.rb +111 -0
- data/lib/data_objects/uri.rb +109 -0
- data/lib/data_objects/utilities.rb +18 -0
- data/lib/data_objects/version.rb +3 -0
- data/lib/data_objects.rb +20 -0
- data/spec/command_spec.rb +24 -0
- data/spec/connection_spec.rb +31 -0
- data/spec/do_mock.rb +29 -0
- data/spec/do_mock2.rb +29 -0
- data/spec/pooling_spec.rb +153 -0
- data/spec/reader_spec.rb +19 -0
- data/spec/result_spec.rb +21 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/transaction_spec.rb +37 -0
- data/spec/uri_spec.rb +23 -0
- data/tasks/release.rake +14 -0
- data/tasks/spec.rake +10 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 244576946e544f5a11ca625f783d1a5536e77bef7437041fdb11d6abe793094f
|
4
|
+
data.tar.gz: 8583773433efe291633740bd2499c404d81cb6f444705e9f45d947ee709697c6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7113134178dc9b9675801b387a91508864e2002a27a2d8c19de95edeebb82baa7b46a804a480a9738b2dd803724ec9f7d21224fcd028c718e7983ce9423ffb6a
|
7
|
+
data.tar.gz: 873820bd19ce5202a58c8ad2a0174de99ae1e04e23ce803b1c30a794a6e1c813fa601c823440c2094f0cebb07b2bb6dd5e01a94e46e2c236f2e53715f01ce4c8
|
data/ChangeLog.markdown
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
## 0.10.17 2016-01-24
|
2
|
+
|
3
|
+
* Enable CI with Ruby 2.3
|
4
|
+
* Improve utf8mb4 support in do\_mysql
|
5
|
+
* Fix memory leak in do\_mysql and do\_postgres
|
6
|
+
* Support for MySQL 5.7 in do\_mysql
|
7
|
+
* Fix bug with DateTime and wrong timezone offsets
|
8
|
+
* Fix compilation on JRuby 9000
|
9
|
+
|
10
|
+
## 0.10.16 2015-05-17
|
11
|
+
|
12
|
+
* Fix compile issue with do\_postgres on stock OS X Ruby
|
13
|
+
|
14
|
+
## 0.10.15 2015-02-15
|
15
|
+
|
16
|
+
* Ruby 2.2 support
|
17
|
+
* Double after free fix in do\_postgres
|
18
|
+
* utf8mb4 support on do\_mysql
|
19
|
+
|
20
|
+
* Windows support on 2.1.x and 2.2.x
|
21
|
+
## 0.10.14 2014-02-13
|
22
|
+
|
23
|
+
* Don't do DNS lookup in transaction loading
|
24
|
+
|
25
|
+
## 0.10.13 2013-05-27
|
26
|
+
|
27
|
+
* Create binaries for Ruby 2.0 on Windows
|
28
|
+
* Fix segfault in do\_postgres
|
29
|
+
* Fix compilation of do\_oracle for Ruby 2.0
|
30
|
+
|
31
|
+
## 0.10.12 2013-01-21
|
32
|
+
|
33
|
+
* Fix JRuby driver loading for newer jdbc-\* gems
|
34
|
+
* Compatibility change for anscient MySQL versions
|
35
|
+
|
36
|
+
## 0.10.11 2012-12-29
|
37
|
+
|
38
|
+
* Rename C symbols to prevent name collitions
|
39
|
+
|
40
|
+
## 0.10.10 2012-10-11
|
41
|
+
|
42
|
+
* JRuby performance improvements
|
43
|
+
* Reconnect added on JRuby
|
44
|
+
* do\_sqlite3 C driver supports busy\_timeout
|
45
|
+
|
46
|
+
## 0.10.9 2012-08-13
|
47
|
+
|
48
|
+
* Don't try to escape queries when no binding parameters are given
|
49
|
+
|
50
|
+
## 0.10.8 2012-02-10
|
51
|
+
|
52
|
+
* Ruby 1.9.3 compatibility on Windows
|
53
|
+
* Don't display password in URI
|
54
|
+
|
55
|
+
## 0.10.7 2011-10-13
|
56
|
+
|
57
|
+
* Ruby 1.9.3 compatibility
|
58
|
+
|
59
|
+
## 0.10.6 2011-05-22
|
60
|
+
|
61
|
+
Bugfixes
|
62
|
+
* Fix an issue on some platforms when multiple DO drivers are loaded
|
63
|
+
|
64
|
+
## 0.10.5 2011-05-03
|
65
|
+
|
66
|
+
Bugfixes
|
67
|
+
* Fix an issue with DateTime (do\_sqlite3)
|
68
|
+
|
69
|
+
## 0.10.4 2011-04-28
|
70
|
+
|
71
|
+
New features
|
72
|
+
* Add save point to transactions (all)
|
73
|
+
* JRuby 1.9 mode support (encodings etc.)
|
74
|
+
|
75
|
+
Bugfixes
|
76
|
+
* Fix segfault when no tuples are returned from a non select statement (do\_postgres)
|
77
|
+
* Fix bug when using nested transactions in concurrent scenarios (all)
|
78
|
+
* Use column aliases instead of names (jruby)
|
79
|
+
* DST calculation fixes (all)
|
80
|
+
* Attempt to add better support for ancient MySQL versions (do\_mysql)
|
81
|
+
* Fix handling sub second precision for Time objects (do\_postgres)
|
82
|
+
|
83
|
+
Other
|
84
|
+
* Refactor to DRY up the adapters (all)
|
85
|
+
* Many style fixes
|
86
|
+
* Switch back to RSpec
|
87
|
+
|
88
|
+
## 0.10.3 2011-01-30
|
89
|
+
* Reworked transactions
|
90
|
+
* Fix a DST bug that could cause datetimes in the wrong timezone
|
91
|
+
|
92
|
+
## 0.10.2 2010-05-19
|
93
|
+
* Support for Encoding.default_internal
|
94
|
+
* Rework logging to adding a callback is possible
|
95
|
+
|
96
|
+
## 0.10.1 2010-01-08
|
97
|
+
|
98
|
+
* Removal of Extlib dependency: Pooling and Utilities code moved to DataObjects.
|
99
|
+
* Switch to Jeweler for Gem building tasks (this change may be temporary).
|
100
|
+
* Switch to using Bacon for running specs: This should make specs friendlier to
|
101
|
+
new Ruby implementations that are not yet 100% MRI-compatible, and in turn,
|
102
|
+
pave the road for our own IronRuby and MacRuby support.
|
103
|
+
* Make DataObjects::Reader Enumerable.
|
104
|
+
|
105
|
+
## 0.10.0 2009-09-15
|
106
|
+
|
107
|
+
* No Changes since 0.9.11
|
108
|
+
|
109
|
+
## 0.9.11 2009-01-19
|
110
|
+
* Fixes
|
111
|
+
* Use Extlib `Object.full_const_get` instead of custom code
|
112
|
+
* Remove Field as it was unused
|
113
|
+
|
114
|
+
## 0.9.9 2008-11-27
|
115
|
+
* No Changes since 0.9.8
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 - 2011 Yehuda Katz, Dirkjan Bussink
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# data_objects
|
2
|
+
|
3
|
+
* <http://dataobjects.info>
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
A unified Ruby API for popular databases.
|
8
|
+
|
9
|
+
## License
|
10
|
+
|
11
|
+
Licensed under the MIT license. Please see the {file:LICENSE} for more information.
|
12
|
+
|
13
|
+
## Contact
|
14
|
+
|
15
|
+
**IRC**: **Join us on IRC in #datamapper on irc.freenode.net!**<br/>
|
16
|
+
**Git**: <http://github.com/firesring/datamapper-do><br/>
|
17
|
+
**Author**: Dirkjan Bussink<br/>
|
18
|
+
**License**: MIT License
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler'
|
4
|
+
require 'rubygems/package_task'
|
5
|
+
Bundler::GemHelper.install_tasks
|
6
|
+
|
7
|
+
require 'rake'
|
8
|
+
require 'rake/clean'
|
9
|
+
|
10
|
+
ROOT = Pathname(__FILE__).dirname.expand_path
|
11
|
+
|
12
|
+
# rubocop:disable Style/StringConcatenation
|
13
|
+
require ROOT + 'lib/data_objects/version'
|
14
|
+
# rubocop:enable Style/StringConcatenation
|
15
|
+
|
16
|
+
SUDO = 'sudo' unless ENV['SUDOLESS']
|
17
|
+
|
18
|
+
CLEAN.include(%w(pkg/ **/*.rbc))
|
19
|
+
|
20
|
+
FileList['tasks/**/*.rake'].each { |task| import task }
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module DataObjects
|
2
|
+
# Abstract base class for adapter-specific Command subclasses
|
3
|
+
class Command
|
4
|
+
# The Connection on which the command will be run
|
5
|
+
attr_reader :connection
|
6
|
+
|
7
|
+
# Create a new Command object on the specified connection
|
8
|
+
def initialize(connection, text)
|
9
|
+
raise ArgumentError, '+connection+ must be a DataObjects::Connection' unless connection.is_a?(DataObjects::Connection)
|
10
|
+
|
11
|
+
@connection = connection
|
12
|
+
@text = text
|
13
|
+
end
|
14
|
+
|
15
|
+
# Execute this command and return no dataset
|
16
|
+
def execute_non_query(*_args)
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
# Execute this command and return a DataObjects::Reader for a dataset
|
21
|
+
def execute_reader(*_args)
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
# Assign an array of types for the columns to be returned by this command
|
26
|
+
def set_types(_column_types)
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
# Display the command text
|
31
|
+
def to_s
|
32
|
+
@text
|
33
|
+
end
|
34
|
+
|
35
|
+
# Escape a string of SQL with a set of arguments.
|
36
|
+
# The first argument is assumed to be the SQL to escape,
|
37
|
+
# the remaining arguments (if any) are assumed to be
|
38
|
+
# values to escape and interpolate.
|
39
|
+
#
|
40
|
+
# ==== Examples
|
41
|
+
# escape_sql("SELECT * FROM zoos")
|
42
|
+
# # => "SELECT * FROM zoos"
|
43
|
+
#
|
44
|
+
# escape_sql("SELECT * FROM zoos WHERE name = ?", "Dallas")
|
45
|
+
# # => "SELECT * FROM zoos WHERE name = `Dallas`"
|
46
|
+
#
|
47
|
+
# escape_sql("SELECT * FROM zoos WHERE name = ? AND acreage > ?", "Dallas", 40)
|
48
|
+
# # => "SELECT * FROM zoos WHERE name = `Dallas` AND acreage > 40"
|
49
|
+
#
|
50
|
+
# ==== Warning
|
51
|
+
# This method is meant mostly for adapters that don't support
|
52
|
+
# bind-parameters.
|
53
|
+
private def escape_sql(args)
|
54
|
+
return @text if args.empty?
|
55
|
+
|
56
|
+
sql = @text.dup
|
57
|
+
vars = args.dup
|
58
|
+
|
59
|
+
replacements = 0
|
60
|
+
mismatch = false
|
61
|
+
|
62
|
+
sql.gsub!(/'[^']*'|"[^"]*"|`[^`]*`|\?/) do |x|
|
63
|
+
next x unless x == '?'
|
64
|
+
|
65
|
+
replacements += 1
|
66
|
+
if vars.empty?
|
67
|
+
mismatch = true
|
68
|
+
else
|
69
|
+
var = vars.shift
|
70
|
+
connection.quote_value(var)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
raise ArgumentError, "Binding mismatch: #{args.size} for #{replacements}" if !vars.empty? || mismatch
|
75
|
+
|
76
|
+
sql
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
|
2
|
+
module DataObjects
|
3
|
+
# An abstract connection to a DataObjects resource. The physical connection may be broken and re-established from time to time.
|
4
|
+
class Connection
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
# Make a connection to the database using the DataObjects::URI given.
|
8
|
+
# Note that the physical connection may be delayed until the first command is issued, so success here doesn't necessarily mean you can connect.
|
9
|
+
def self.new(uri_s)
|
10
|
+
uri = DataObjects::URI.parse(uri_s)
|
11
|
+
|
12
|
+
driver = uri.scheme
|
13
|
+
conn_uri = uri
|
14
|
+
|
15
|
+
# Exceptions to how a driver class is determined for a given URI
|
16
|
+
driver_class = driver.capitalize
|
17
|
+
|
18
|
+
clazz = DataObjects.const_get(driver_class)::Connection
|
19
|
+
unless clazz.method_defined? :close
|
20
|
+
clazz.class_eval do
|
21
|
+
include Pooling
|
22
|
+
alias_method :close, :release
|
23
|
+
end
|
24
|
+
end
|
25
|
+
clazz.new(conn_uri)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Ensure that all Connection subclasses handle pooling and logging uniformly.
|
29
|
+
# See also DataObjects::Pooling and DataObjects::Logger
|
30
|
+
def self.inherited(target)
|
31
|
+
target.class_eval do
|
32
|
+
# Allocate a Connection object from the pool, creating one if necessary. This method is active in Connection subclasses only.
|
33
|
+
def self.new(*args)
|
34
|
+
instance = allocate
|
35
|
+
instance.send(:initialize, *args)
|
36
|
+
instance
|
37
|
+
end
|
38
|
+
|
39
|
+
include Quoting
|
40
|
+
end
|
41
|
+
|
42
|
+
return unless (driver_module_name = target.name.split('::')[-2])
|
43
|
+
|
44
|
+
driver_module = DataObjects.const_get(driver_module_name)
|
45
|
+
driver_module.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
46
|
+
def self.logger
|
47
|
+
@logger
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.logger=(logger)
|
51
|
+
@logger = logger
|
52
|
+
end
|
53
|
+
EOS
|
54
|
+
|
55
|
+
driver_module.logger = DataObjects::Logger.new(nil, :off)
|
56
|
+
end
|
57
|
+
|
58
|
+
#####################################################
|
59
|
+
# Standard API Definition
|
60
|
+
#####################################################
|
61
|
+
|
62
|
+
# Show the URI for this connection, without
|
63
|
+
# the password the connection was setup with
|
64
|
+
def to_s
|
65
|
+
@uri.to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
def initialize(_uri) # :nodoc:
|
69
|
+
raise NotImplementedError
|
70
|
+
end
|
71
|
+
|
72
|
+
def dispose # :nodoc:
|
73
|
+
raise NotImplementedError
|
74
|
+
end
|
75
|
+
|
76
|
+
# Create a Command object of the right subclass using the given text
|
77
|
+
def create_command(text)
|
78
|
+
self.class.concrete_command.new(self, text)
|
79
|
+
end
|
80
|
+
|
81
|
+
def extension
|
82
|
+
driver_namespace.const_get('Extension').new(self)
|
83
|
+
end
|
84
|
+
|
85
|
+
private def driver_namespace
|
86
|
+
constant_name = self.class.name&.split('::')
|
87
|
+
DataObjects.const_get(constant_name[-2])
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.concrete_command
|
91
|
+
constant_name = name&.split('::')
|
92
|
+
@concrete_command ||= DataObjects.const_get(constant_name[-2]).const_get('Command')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module DataObjects
|
2
|
+
class SQLError < Error
|
3
|
+
attr_reader :message, :code, :sqlstate, :query, :uri
|
4
|
+
|
5
|
+
def initialize(message, code = nil, sqlstate = nil, query = nil, uri = nil)
|
6
|
+
@message = message
|
7
|
+
@code = code
|
8
|
+
@sqlstate = sqlstate
|
9
|
+
@query = query
|
10
|
+
@uri = uri
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"#{message} (code: #{code}, sql state: #{sqlstate}, query: #{query}, uri: #{uri})"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
require 'time' # httpdate
|
2
|
+
|
3
|
+
module DataObjects
|
4
|
+
module Logging
|
5
|
+
def log(message)
|
6
|
+
logger = driver_namespace.logger
|
7
|
+
return unless logger.level <= DataObjects::Logger::LEVELS[:debug]
|
8
|
+
|
9
|
+
message = format('(%.6f) %s', message.duration / 1_000_000.0, message.query)
|
10
|
+
logger.debug message
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# The global logger for DataObjects
|
16
|
+
attr_accessor :logger
|
17
|
+
end
|
18
|
+
|
19
|
+
# ==== Public DataObjects Logger API
|
20
|
+
#
|
21
|
+
# Logger taken from Merb :)
|
22
|
+
#
|
23
|
+
# To replace an existing logger with a new one:
|
24
|
+
# DataObjects::Logger.set_log(log{String, IO},level{Symbol, String})
|
25
|
+
#
|
26
|
+
# Available logging levels are
|
27
|
+
# DataObjects::Logger::{ Fatal, Error, Warn, Info, Debug }
|
28
|
+
#
|
29
|
+
# Logging via:
|
30
|
+
# DataObjects.logger.fatal(message<String>)
|
31
|
+
# DataObjects.logger.error(message<String>)
|
32
|
+
# DataObjects.logger.warn(message<String>)
|
33
|
+
# DataObjects.logger.info(message<String>)
|
34
|
+
# DataObjects.logger.debug(message<String>)
|
35
|
+
#
|
36
|
+
# Flush the buffer to
|
37
|
+
# DataObjects.logger.flush
|
38
|
+
#
|
39
|
+
# Remove the current log object
|
40
|
+
# DataObjects.logger.close
|
41
|
+
#
|
42
|
+
# ==== Private DataObjects Logger API
|
43
|
+
#
|
44
|
+
# To initialize the logger you create a new object, proxies to set_log.
|
45
|
+
# DataObjects::Logger.new(log{String, IO},level{Symbol, String})
|
46
|
+
#
|
47
|
+
# Logger will not create the file until something is actually logged
|
48
|
+
# This avoids file creation on DataObjects init when it creates the
|
49
|
+
# default logger.
|
50
|
+
class Logger
|
51
|
+
# Use asynchronous I/O?
|
52
|
+
attr_accessor :aio
|
53
|
+
# delimiter to use between message sections
|
54
|
+
attr_accessor :delimiter
|
55
|
+
# a symbol representing the log level from {:off, :fatal, :error, :warn, :info, :debug}
|
56
|
+
attr_reader :level
|
57
|
+
# Direct access to the buffer
|
58
|
+
attr_reader :buffer
|
59
|
+
# The name of the log file
|
60
|
+
attr_reader :log
|
61
|
+
|
62
|
+
Message = Struct.new(:query, :start, :duration)
|
63
|
+
|
64
|
+
#
|
65
|
+
# Ruby (standard) logger levels:
|
66
|
+
# off: absolutely nothing
|
67
|
+
# fatal: an unhandleable error that results in a program crash
|
68
|
+
# error: a handleable error condition
|
69
|
+
# warn: a warning
|
70
|
+
# info: generic (useful) information about system operation
|
71
|
+
# debug: low-level information for developers
|
72
|
+
#
|
73
|
+
# DataObjects::Logger::LEVELS[:off, :fatal, :error, :warn, :info, :debug]
|
74
|
+
LEVELS =
|
75
|
+
{
|
76
|
+
off: 99_999,
|
77
|
+
fatal: 7,
|
78
|
+
error: 6,
|
79
|
+
warn: 4,
|
80
|
+
info: 3,
|
81
|
+
debug: 0
|
82
|
+
}.freeze
|
83
|
+
|
84
|
+
# Set the log level (use the level symbols as documented)
|
85
|
+
def level=(new_level)
|
86
|
+
@level = LEVELS[new_level.to_sym]
|
87
|
+
reset_methods(:close)
|
88
|
+
end
|
89
|
+
|
90
|
+
# The idea here is that instead of performing an 'if' conditional check on
|
91
|
+
# each logging we do it once when the log object is setup
|
92
|
+
private def set_write_method
|
93
|
+
@log.instance_eval do
|
94
|
+
# Determine if asynchronous IO can be used
|
95
|
+
def aio?
|
96
|
+
@aio = !RUBY_PLATFORM.match(/java|mswin/) &&
|
97
|
+
@log != $stdout &&
|
98
|
+
@log.respond_to?(:write_nonblock)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Define the write method based on if aio an be used
|
102
|
+
undef write_method if defined? write_method
|
103
|
+
if aio?
|
104
|
+
alias :write_method :write_nonblock
|
105
|
+
else
|
106
|
+
alias :write_method :write
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private def initialize_log(log)
|
112
|
+
close if @log # be sure that we don't leave open files laying around.
|
113
|
+
@log = log || 'log/dm.log'
|
114
|
+
end
|
115
|
+
|
116
|
+
private def reset_methods(o_or_c)
|
117
|
+
if o_or_c == :open
|
118
|
+
alias internal_push push_opened
|
119
|
+
elsif o_or_c == :close
|
120
|
+
alias internal_push push_closed
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
private def push_opened(string)
|
125
|
+
message = Time.now.httpdate
|
126
|
+
message << delimiter
|
127
|
+
message << string
|
128
|
+
message << "\n" unless message[-1] == "\n"
|
129
|
+
@buffer << message
|
130
|
+
flush # Force a flush for now until we figure out where we want to use the buffering.
|
131
|
+
end
|
132
|
+
|
133
|
+
private def push_closed(string)
|
134
|
+
unless @log.respond_to?(:write)
|
135
|
+
log = Pathname(@log)
|
136
|
+
log.dirname.mkpath
|
137
|
+
@log = log.open('a')
|
138
|
+
@log.sync = true
|
139
|
+
end
|
140
|
+
set_write_method
|
141
|
+
reset_methods(:open)
|
142
|
+
push(string)
|
143
|
+
end
|
144
|
+
|
145
|
+
alias internal_push push_closed
|
146
|
+
|
147
|
+
private def prep_msg(message, level)
|
148
|
+
level << delimiter << message
|
149
|
+
end
|
150
|
+
|
151
|
+
# To initialize the logger you create a new object, proxies to set_log.
|
152
|
+
# DataObjects::Logger.new(log{String, IO},level{Symbol, String})
|
153
|
+
#
|
154
|
+
# @param * [Mixed]
|
155
|
+
# log<IO,String> either an IO object or a name of a logfile.
|
156
|
+
# log_level<String> the message string to be logged
|
157
|
+
# delimiter<String> delimiter to use between message sections
|
158
|
+
# log_creation<Boolean> log that the file is being created
|
159
|
+
public def initialize(*args)
|
160
|
+
set_log(*args)
|
161
|
+
end
|
162
|
+
|
163
|
+
# To replace an existing logger with a new one:
|
164
|
+
# DataObjects::Logger.set_log(log{String, IO},level{Symbol, String})
|
165
|
+
#
|
166
|
+
#
|
167
|
+
# @param log<IO,String> either an IO object or a name of a logfile.
|
168
|
+
# @param log_level<Symbol> a symbol representing the log level from
|
169
|
+
# {:off, :fatal, :error, :warn, :info, :debug}
|
170
|
+
# @param delimiter<String> delimiter to use between message sections
|
171
|
+
# @param log_creation<Boolean> log that the file is being created
|
172
|
+
public def set_log(log, log_level = :off, delimiter = ' ~ ', log_creation = false)
|
173
|
+
delimiter ||= ' ~ '
|
174
|
+
|
175
|
+
self.level = if log_level && LEVELS[log_level.to_sym]
|
176
|
+
log_level.to_sym
|
177
|
+
else
|
178
|
+
:debug
|
179
|
+
end
|
180
|
+
|
181
|
+
@buffer = []
|
182
|
+
@delimiter = delimiter
|
183
|
+
|
184
|
+
initialize_log(log)
|
185
|
+
|
186
|
+
DataObjects.logger = self
|
187
|
+
|
188
|
+
info('Logfile created') if log_creation
|
189
|
+
end
|
190
|
+
|
191
|
+
# Flush the entire buffer to the log object.
|
192
|
+
# DataObjects.logger.flush
|
193
|
+
#
|
194
|
+
public def flush
|
195
|
+
return unless @buffer.size.positive?
|
196
|
+
|
197
|
+
@log.write_method(@buffer.slice!(0..-1).join)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Close and remove the current log object.
|
201
|
+
# DataObjects.logger.close
|
202
|
+
#
|
203
|
+
public def close
|
204
|
+
flush
|
205
|
+
@log.close if @log.respond_to?(:close)
|
206
|
+
@log = nil
|
207
|
+
end
|
208
|
+
|
209
|
+
# Appends a string and log level to logger's buffer.
|
210
|
+
|
211
|
+
#
|
212
|
+
# Note that the string is discarded if the string's log level less than the
|
213
|
+
# logger's log level.
|
214
|
+
#
|
215
|
+
# Note that if the logger is aio capable then the logger will use
|
216
|
+
# non-blocking asynchronous writes.
|
217
|
+
#
|
218
|
+
# Is this old or does the method receive mixed params? level<Fixnum> the logging level as an integer
|
219
|
+
# @param string<String> the message string to be logged
|
220
|
+
public def push(string)
|
221
|
+
internal_push(string)
|
222
|
+
end
|
223
|
+
alias << push
|
224
|
+
|
225
|
+
# Generate the following logging methods for DataObjects.logger as described
|
226
|
+
# in the API:
|
227
|
+
# :fatal, :error, :warn, :info, :debug
|
228
|
+
# :off only gets an off? method
|
229
|
+
LEVELS.each_pair do |name, number|
|
230
|
+
unless name.to_sym == :off
|
231
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
232
|
+
# DOC
|
233
|
+
def #{name}(message)
|
234
|
+
self.<<( prep_msg(message, "#{name}") ) if #{name}?
|
235
|
+
end
|
236
|
+
EOS
|
237
|
+
end
|
238
|
+
|
239
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
240
|
+
# DOC
|
241
|
+
def #{name}?
|
242
|
+
#{number} >= level
|
243
|
+
end
|
244
|
+
EOS
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|