data_objects 0.2.0 → 0.9.2

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.
@@ -0,0 +1,30 @@
1
+ module DataObjects
2
+ class Command
3
+
4
+ attr_reader :connection
5
+
6
+ # initialize creates a new Command object
7
+ def initialize(connection, text)
8
+ raise ArgumentError.new("+connection+ must be a DataObjects::Connection") unless DataObjects::Connection === connection
9
+ @connection, @text = connection, text
10
+ end
11
+
12
+ def execute_non_query(*args)
13
+ raise NotImplementedError.new
14
+ end
15
+
16
+ def execute_reader(*args)
17
+ raise NotImplementedError.new
18
+ end
19
+
20
+ def set_types(column_types)
21
+ raise NotImplementedError.new
22
+ end
23
+
24
+ def to_s
25
+ @text
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,88 @@
1
+ require 'addressable/uri'
2
+ require 'set'
3
+
4
+ begin
5
+ require 'fastthread'
6
+ rescue LoadError
7
+ end
8
+
9
+ module DataObjects
10
+ class Connection
11
+
12
+ def self.new(uri)
13
+ uri = uri.is_a?(String) ? Addressable::URI::parse(uri) : uri
14
+
15
+ if uri.scheme == 'jdbc'
16
+ driver_name = uri.path.split(':').first
17
+ else
18
+ driver_name = uri.scheme.capitalize
19
+ end
20
+
21
+ DataObjects.const_get(driver_name.capitalize)::Connection.new(uri)
22
+ end
23
+
24
+ def self.inherited(target)
25
+ target.class_eval do
26
+
27
+ def self.new(*args)
28
+ instance = allocate
29
+ instance.send(:initialize, *args)
30
+ instance
31
+ end
32
+
33
+ include Extlib::Pooling
34
+ alias close release
35
+ end
36
+
37
+ if driver_module_name = target.name.split('::')[-2]
38
+ driver_module = DataObjects::const_get(driver_module_name)
39
+ driver_module.class_eval <<-EOS
40
+ def self.logger
41
+ @logger
42
+ end
43
+
44
+ def self.logger=(logger)
45
+ @logger = logger
46
+ end
47
+ EOS
48
+
49
+ driver_module.logger = DataObjects::Logger.new(nil, :off)
50
+ end
51
+ end
52
+
53
+ #####################################################
54
+ # Standard API Definition
55
+ #####################################################
56
+ def to_s
57
+ @uri.to_s
58
+ end
59
+
60
+ def initialize(uri)
61
+ raise NotImplementedError.new
62
+ end
63
+
64
+ def dispose
65
+ raise NotImplementedError.new
66
+ end
67
+
68
+ def create_command(text)
69
+ concrete_command.new(self, text)
70
+ end
71
+
72
+ private
73
+ def concrete_command
74
+ @concrete_command || begin
75
+
76
+ class << self
77
+ private
78
+ def concrete_command
79
+ @concrete_command
80
+ end
81
+ end
82
+
83
+ @concrete_command = DataObjects::const_get(self.class.name.split('::')[-2]).const_get('Command')
84
+ end
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,19 @@
1
+ module DataObjects
2
+
3
+ class Field
4
+
5
+ def initialize(name, type)
6
+ @name, @type = name, type
7
+ end
8
+
9
+ def name
10
+ @name
11
+ end
12
+
13
+ def type
14
+ @type
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,233 @@
1
+ require "time" # httpdate
2
+ # ==== Public DataObjects Logger API
3
+ #
4
+ # Logger taken from Merb :)
5
+ #
6
+ # To replace an existing logger with a new one:
7
+ # DataObjects::Logger.set_log(log{String, IO},level{Symbol, String})
8
+ #
9
+ # Available logging levels are
10
+ # DataObjects::Logger::{ Fatal, Error, Warn, Info, Debug }
11
+ #
12
+ # Logging via:
13
+ # DataObjects.logger.fatal(message<String>)
14
+ # DataObjects.logger.error(message<String>)
15
+ # DataObjects.logger.warn(message<String>)
16
+ # DataObjects.logger.info(message<String>)
17
+ # DataObjects.logger.debug(message<String>)
18
+ #
19
+ # Flush the buffer to
20
+ # DataObjects.logger.flush
21
+ #
22
+ # Remove the current log object
23
+ # DataObjects.logger.close
24
+ #
25
+ # ==== Private DataObjects Logger API
26
+ #
27
+ # To initialize the logger you create a new object, proxies to set_log.
28
+ # DataObjects::Logger.new(log{String, IO},level{Symbol, String})
29
+ #
30
+ # Logger will not create the file until something is actually logged
31
+ # This avoids file creation on DataObjects init when it creates the
32
+ # default logger.
33
+ module DataObjects
34
+
35
+ class << self #:nodoc:
36
+ attr_accessor :logger
37
+ end
38
+
39
+ class Logger
40
+
41
+ attr_accessor :aio
42
+ attr_accessor :delimiter
43
+ attr_reader :level
44
+ attr_reader :buffer
45
+ attr_reader :log
46
+
47
+ # @note
48
+ # Ruby (standard) logger levels:
49
+ # off: absolutely nothing
50
+ # fatal: an unhandleable error that results in a program crash
51
+ # error: a handleable error condition
52
+ # warn: a warning
53
+ # info: generic (useful) information about system operation
54
+ # debug: low-level information for developers
55
+ #
56
+ # DataObjects::Logger::LEVELS[:off, :fatal, :error, :warn, :info, :debug]
57
+ LEVELS =
58
+ {
59
+ :off => 99999,
60
+ :fatal => 7,
61
+ :error => 6,
62
+ :warn => 4,
63
+ :info => 3,
64
+ :debug => 0
65
+ }
66
+
67
+ def level=(new_level)
68
+ @level = LEVELS[new_level.to_sym]
69
+ reset_methods(:close)
70
+ end
71
+
72
+ private
73
+
74
+ # The idea here is that instead of performing an 'if' conditional check on
75
+ # each logging we do it once when the log object is setup
76
+ def set_write_method
77
+ @log.instance_eval do
78
+
79
+ # Determine if asynchronous IO can be used
80
+ def aio?
81
+ @aio = !RUBY_PLATFORM.match(/java|mswin/) &&
82
+ !(@log == STDOUT) &&
83
+ @log.respond_to?(:write_nonblock)
84
+ end
85
+
86
+ # Define the write method based on if aio an be used
87
+ undef write_method if defined? write_method
88
+ if aio?
89
+ alias :write_method :write_nonblock
90
+ else
91
+ alias :write_method :write
92
+ end
93
+ end
94
+ end
95
+
96
+ def initialize_log(log)
97
+ close if @log # be sure that we don't leave open files laying around.
98
+ @log = log || "log/dm.log"
99
+ end
100
+
101
+ def reset_methods(o_or_c)
102
+ if o_or_c == :open
103
+ alias internal_push push_opened
104
+ elsif o_or_c == :close
105
+ alias internal_push push_closed
106
+ end
107
+ end
108
+
109
+ def push_opened(string)
110
+ message = Time.now.httpdate
111
+ message << delimiter
112
+ message << string
113
+ message << "\n" unless message[-1] == ?\n
114
+ @buffer << message
115
+ flush # Force a flush for now until we figure out where we want to use the buffering.
116
+ end
117
+
118
+ def push_closed(string)
119
+ unless @log.respond_to?(:write)
120
+ log = Pathname(@log)
121
+ log.dirname.mkpath
122
+ @log = log.open('a')
123
+ @log.sync = true
124
+ end
125
+ set_write_method
126
+ reset_methods(:open)
127
+ push(string)
128
+ end
129
+
130
+ alias internal_push push_closed
131
+
132
+ def prep_msg(message, level)
133
+ level << delimiter << message
134
+ end
135
+
136
+ public
137
+
138
+ # To initialize the logger you create a new object, proxies to set_log.
139
+ # DataObjects::Logger.new(log{String, IO},level{Symbol, String})
140
+ #
141
+ # @param log<IO,String> either an IO object or a name of a logfile.
142
+ # @param log_level<String> the message string to be logged
143
+ # @param delimiter<String> delimiter to use between message sections
144
+ # @param log_creation<Boolean> log that the file is being created
145
+ def initialize(*args)
146
+ set_log(*args)
147
+ end
148
+
149
+ # To replace an existing logger with a new one:
150
+ # DataObjects::Logger.set_log(log{String, IO},level{Symbol, String})
151
+ #
152
+ #
153
+ # @param log<IO,String> either an IO object or a name of a logfile.
154
+ # @param log_level<Symbol> a symbol representing the log level from
155
+ # {:off, :fatal, :error, :warn, :info, :debug}
156
+ # @param delimiter<String> delimiter to use between message sections
157
+ # @param log_creation<Boolean> log that the file is being created
158
+ def set_log(log, log_level = :off, delimiter = " ~ ", log_creation = false)
159
+ delimiter ||= " ~ "
160
+
161
+ if log_level && LEVELS[log_level.to_sym]
162
+ self.level = log_level.to_sym
163
+ else
164
+ self.level = :debug
165
+ end
166
+
167
+ @buffer = []
168
+ @delimiter = delimiter
169
+
170
+ initialize_log(log)
171
+
172
+ DataObjects.logger = self
173
+
174
+ self.info("Logfile created") if log_creation
175
+ end
176
+
177
+ # Flush the entire buffer to the log object.
178
+ # DataObjects.logger.flush
179
+ #
180
+ def flush
181
+ return unless @buffer.size > 0
182
+ @log.write_method(@buffer.slice!(0..-1).to_s)
183
+ end
184
+
185
+ # Close and remove the current log object.
186
+ # DataObjects.logger.close
187
+ #
188
+ def close
189
+ flush
190
+ @log.close if @log.respond_to?(:close)
191
+ @log = nil
192
+ end
193
+
194
+ # Appends a string and log level to logger's buffer.
195
+
196
+ # @note
197
+ # Note that the string is discarded if the string's log level less than the
198
+ # logger's log level.
199
+ # @note
200
+ # Note that if the logger is aio capable then the logger will use
201
+ # non-blocking asynchronous writes.
202
+ #
203
+ # @param level<Fixnum> the logging level as an integer
204
+ # @param string<String> the message string to be logged
205
+ def push(string)
206
+ internal_push(string)
207
+ end
208
+ alias << push
209
+
210
+ # Generate the following logging methods for DataObjects.logger as described
211
+ # in the API:
212
+ # :fatal, :error, :warn, :info, :debug
213
+ # :off only gets an off? method
214
+ LEVELS.each_pair do |name, number|
215
+ unless name.to_sym == :off
216
+ class_eval <<-EOS
217
+ # DOC
218
+ def #{name}(message)
219
+ self.<<( prep_msg(message, "#{name}") ) if #{name}?
220
+ end
221
+ EOS
222
+ end
223
+
224
+ class_eval <<-EOS
225
+ # DOC
226
+ def #{name}?
227
+ #{number} >= level
228
+ end
229
+ EOS
230
+ end
231
+
232
+ end # class Logger
233
+ end # module DataObjects
@@ -0,0 +1,98 @@
1
+ module DataObjects
2
+
3
+ module Quoting
4
+ # Escape a string of SQL with a set of arguments.
5
+ # The first argument is assumed to be the SQL to escape,
6
+ # the remaining arguments (if any) are assumed to be
7
+ # values to escape and interpolate.
8
+ #
9
+ # ==== Examples
10
+ # escape_sql("SELECT * FROM zoos")
11
+ # # => "SELECT * FROM zoos"
12
+ #
13
+ # escape_sql("SELECT * FROM zoos WHERE name = ?", "Dallas")
14
+ # # => "SELECT * FROM zoos WHERE name = `Dallas`"
15
+ #
16
+ # escape_sql("SELECT * FROM zoos WHERE name = ? AND acreage > ?", "Dallas", 40)
17
+ # # => "SELECT * FROM zoos WHERE name = `Dallas` AND acreage > 40"
18
+ #
19
+ # ==== Warning
20
+ # This method is meant mostly for adapters that don't support
21
+ # bind-parameters.
22
+ def escape_sql(args)
23
+ sql = @text.dup
24
+
25
+ unless args.empty?
26
+ sql.gsub!(/\?/) do |x|
27
+ quote_value(args.shift)
28
+ end
29
+ end
30
+
31
+ sql
32
+ end
33
+
34
+ def quote_value(value)
35
+ return 'NULL' if value.nil?
36
+
37
+ case value
38
+ when Numeric then quote_numeric(value)
39
+ when String then quote_string(value)
40
+ when Class then quote_class(value)
41
+ when Time then quote_time(value)
42
+ when DateTime then quote_datetime(value)
43
+ when Date then quote_date(value)
44
+ when TrueClass, FalseClass then quote_boolean(value)
45
+ when Array then quote_array(value)
46
+ when Range then quote_range(value)
47
+ when Symbol then quote_symbol(value)
48
+ else
49
+ if value.respond_to?(:to_sql)
50
+ value.to_sql
51
+ else
52
+ raise "Don't know how to quote #{value.inspect}"
53
+ end
54
+ end
55
+ end
56
+
57
+ def quote_symbol(value)
58
+ quote_string(value.to_s)
59
+ end
60
+
61
+ def quote_numeric(value)
62
+ value.to_s
63
+ end
64
+
65
+ def quote_string(value)
66
+ "'#{value.gsub("'", "''")}'"
67
+ end
68
+
69
+ def quote_class(value)
70
+ quote_string(value.name)
71
+ end
72
+
73
+ def quote_time(value)
74
+ "'#{value.strftime('%Y-%m-%d %H:%M:%S')}" + (value.usec > 0 ? ".#{value.usec.to_s.rjust(6, '0')}'" : "'")
75
+ end
76
+
77
+ def quote_datetime(value)
78
+ "'#{value.dup}'"
79
+ end
80
+
81
+ def quote_date(value)
82
+ "'#{value.strftime("%Y-%m-%d")}'"
83
+ end
84
+
85
+ def quote_boolean(value)
86
+ value.to_s.upcase
87
+ end
88
+
89
+ def quote_array(value)
90
+ "(#{value.map { |entry| quote_value(entry) }.join(', ')})"
91
+ end
92
+
93
+ def quote_range(value)
94
+ "#{quote_value(value.first)} AND #{quote_value(value.last)}"
95
+ end
96
+ end
97
+
98
+ end