data_objects 0.2.0 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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