baza 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/include/dbtime.rb ADDED
@@ -0,0 +1,35 @@
1
+ #This class helps handeling time-columns in databases.
2
+ class Baza::Dbtime
3
+ #These variables return information about the object.
4
+ attr_reader :hours, :mins, :secs, :total_secs
5
+
6
+ #Initializes the object from arguments useually given by Baza::Datarow.
7
+ def initialize(args)
8
+ args = {:time => args} if args.is_a?(String)
9
+
10
+ raise "Invalid arguments given." if !args.is_a?(Hash)
11
+ raise "No time given." if !args[:time]
12
+ raise "Invalid time given." if !args[:time].is_a?(String)
13
+
14
+ match = args[:time].match(/^(\d+):(\d+):(\d+)$/)
15
+ raise "Could not understand time format." if !match
16
+
17
+ @hours = match[1].to_i
18
+ @mins = match[2].to_i
19
+ @secs = match[3].to_i
20
+
21
+ @total_secs = @hours * 3600
22
+ @total_secs += @mins * 60
23
+ @total_secs += @secs
24
+ end
25
+
26
+ #Returns the total amount of hours.
27
+ def hours_total
28
+ return (@total_secs.to_f / 3600)
29
+ end
30
+
31
+ #Return the total amount of minutes.
32
+ def mins_total
33
+ return (@total_secs.to_f / 60)
34
+ end
35
+ end
Binary file
@@ -0,0 +1,604 @@
1
+ class Baza::Driver::Mysql
2
+ attr_reader :knjdb, :conn, :conns, :sep_table, :sep_col, :sep_val
3
+ attr_accessor :tables, :cols, :indexes
4
+
5
+ def initialize(knjdb_ob)
6
+ @knjdb = knjdb_ob
7
+ @opts = @knjdb.opts
8
+ @sep_table = "`"
9
+ @sep_col = "`"
10
+ @sep_val = "'"
11
+
12
+ require "monitor"
13
+ @mutex = Monitor.new
14
+
15
+ if @opts[:encoding]
16
+ @encoding = @opts[:encoding]
17
+ else
18
+ @encoding = "utf8"
19
+ end
20
+
21
+ if @knjdb.opts.key?(:port)
22
+ @port = @knjdb.opts[:port].to_i
23
+ else
24
+ @port = 3306
25
+ end
26
+
27
+ @java_rs_data = {}
28
+ @subtype = @knjdb.opts[:subtype]
29
+ @subtype = "mysql" if @subtype.to_s.length <= 0
30
+ self.reconnect
31
+ end
32
+
33
+ #This method handels the closing of statements and results for the Java MySQL-mode.
34
+ def java_mysql_resultset_killer(id)
35
+ data = @java_rs_data[id]
36
+ return nil if !data
37
+
38
+ data[:res].close
39
+ data[:stmt].close
40
+ @java_rs_data.delete(id)
41
+ end
42
+
43
+ #Cleans the wref-map holding the tables.
44
+ def clean
45
+ self.tables.clean if self.tables
46
+ end
47
+
48
+ #Respawns the connection to the MySQL-database.
49
+ def reconnect
50
+ @mutex.synchronize do
51
+ case @subtype
52
+ when "mysql"
53
+ @conn = Mysql.real_connect(@knjdb.opts[:host], @knjdb.opts[:user], @knjdb.opts[:pass], @knjdb.opts[:db], @port)
54
+ when "mysql2"
55
+ require "rubygems"
56
+ require "mysql2"
57
+
58
+ args = {
59
+ :host => @knjdb.opts[:host],
60
+ :username => @knjdb.opts[:user],
61
+ :password => @knjdb.opts[:pass],
62
+ :database => @knjdb.opts[:db],
63
+ :port => @port,
64
+ :symbolize_keys => true,
65
+ :cache_rows => false
66
+ }
67
+
68
+ #Symbolize keys should also be given here, else table-data wont be symbolized for some reason - knj.
69
+ @query_args = {:symbolize_keys => true}
70
+ @query_args.merge!(@knjdb.opts[:query_args]) if @knjdb.opts[:query_args]
71
+
72
+ pos_args = [:as, :async, :cast_booleans, :database_timezone, :application_timezone, :cache_rows, :connect_flags, :cast]
73
+ pos_args.each do |key|
74
+ args[key] = @knjdb.opts[key] if @knjdb.opts.key?(key)
75
+ end
76
+
77
+ args[:as] = :array if @opts[:result] == "array"
78
+
79
+ tries = 0
80
+ begin
81
+ tries += 1
82
+ @conn = Mysql2::Client.new(args)
83
+ rescue => e
84
+ if tries <= 3
85
+ if e.message == "Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (111)"
86
+ sleep 1
87
+ retry
88
+ end
89
+ end
90
+
91
+ raise e
92
+ end
93
+ when "java"
94
+ if !@jdbc_loaded
95
+ require "java"
96
+ require "/usr/share/java/mysql-connector-java.jar" if File.exists?("/usr/share/java/mysql-connector-java.jar")
97
+ import "com.mysql.jdbc.Driver"
98
+ @jdbc_loaded = true
99
+ end
100
+
101
+ @conn = java.sql::DriverManager.getConnection("jdbc:mysql://#{@knjdb.opts[:host]}:#{@port}/#{@knjdb.opts[:db]}?user=#{@knjdb.opts[:user]}&password=#{@knjdb.opts[:pass]}&populateInsertRowWithDefaultValues=true&zeroDateTimeBehavior=round&characterEncoding=#{@encoding}&holdResultsOpenOverStatementClose=true")
102
+ self.query("SET SQL_MODE = ''")
103
+ else
104
+ raise "Unknown subtype: #{@subtype}"
105
+ end
106
+
107
+ self.query("SET NAMES '#{self.esc(@encoding)}'") if @encoding
108
+ end
109
+ end
110
+
111
+ #Executes a query and returns the result.
112
+ def query(str)
113
+ str = str.to_s
114
+ str = str.force_encoding("UTF-8") if @encoding == "utf8" and str.respond_to?(:force_encoding)
115
+ tries = 0
116
+
117
+ begin
118
+ tries += 1
119
+ @mutex.synchronize do
120
+ case @subtype
121
+ when "mysql"
122
+ return Baza::Driver::Mysql_result.new(self, @conn.query(str))
123
+ when "mysql2"
124
+ return Baza::Driver::Mysql2_result.new(@conn.query(str, @query_args))
125
+ when "java"
126
+ stmt = conn.create_statement
127
+
128
+ if str.match(/^\s*(delete|update|create|drop|insert\s+into|alter)\s+/i)
129
+ begin
130
+ stmt.execute(str)
131
+ ensure
132
+ stmt.close
133
+ end
134
+
135
+ return nil
136
+ else
137
+ id = nil
138
+
139
+ begin
140
+ res = stmt.execute_query(str)
141
+ ret = KnjDB_java_mysql_result.new(@knjdb, @opts, res)
142
+ id = ret.__id__
143
+
144
+ #If ID is being reused we have to free the result.
145
+ self.java_mysql_resultset_killer(id) if @java_rs_data.key?(id)
146
+
147
+ #Save reference to result and statement, so we can close them when they are garbage collected.
148
+ @java_rs_data[id] = {:res => res, :stmt => stmt}
149
+ ObjectSpace.define_finalizer(ret, self.method(:java_mysql_resultset_killer))
150
+
151
+ return ret
152
+ rescue => e
153
+ res.close if res
154
+ stmt.close
155
+ @java_rs_data.delete(id) if ret and id
156
+ raise e
157
+ end
158
+ end
159
+ else
160
+ raise "Unknown subtype: '#{@subtype}'."
161
+ end
162
+ end
163
+ rescue => e
164
+ if tries <= 3
165
+ if e.message == "MySQL server has gone away" or e.message == "closed MySQL connection" or e.message == "Can't connect to local MySQL server through socket"
166
+ sleep 0.5
167
+ self.reconnect
168
+ retry
169
+ elsif e.message.include?("No operations allowed after connection closed") or e.message == "This connection is still waiting for a result, try again once you have the result" or e.message == "Lock wait timeout exceeded; try restarting transaction"
170
+ self.reconnect
171
+ retry
172
+ end
173
+ end
174
+
175
+ raise e
176
+ end
177
+ end
178
+
179
+ #Executes an unbuffered query and returns the result that can be used to access the data.
180
+ def query_ubuf(str)
181
+ @mutex.synchronize do
182
+ case @subtype
183
+ when "mysql"
184
+ @conn.query_with_result = false
185
+ return Baza::Driver::Mysql_unbuffered_result.new(@conn, @opts, @conn.query(str))
186
+ when "mysql2"
187
+ return Baza::Driver::Mysql2_result.new(@conn.query(str, @query_args.merge(:stream => true)))
188
+ when "java"
189
+ if str.match(/^\s*(delete|update|create|drop|insert\s+into)\s+/i)
190
+ stmt = @conn.createStatement
191
+
192
+ begin
193
+ stmt.execute(str)
194
+ ensure
195
+ stmt.close
196
+ end
197
+
198
+ return nil
199
+ else
200
+ stmt = @conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY)
201
+ stmt.setFetchSize(java.lang.Integer::MIN_VALUE)
202
+
203
+ begin
204
+ res = stmt.executeQuery(str)
205
+ ret = KnjDB_java_mysql_result.new(@knjdb, @opts, res)
206
+
207
+ #Save reference to result and statement, so we can close them when they are garbage collected.
208
+ @java_rs_data[ret.__id__] = {:res => res, :stmt => stmt}
209
+ ObjectSpace.define_finalizer(ret, self.method("java_mysql_resultset_killer"))
210
+
211
+ return ret
212
+ rescue => e
213
+ res.close if res
214
+ stmt.close
215
+ raise e
216
+ end
217
+ end
218
+ else
219
+ raise "Unknown subtype: '#{@subtype}'"
220
+ end
221
+ end
222
+ end
223
+
224
+ #Escapes a string to be safe to use in a query.
225
+ def escape_alternative(string)
226
+ case @subtype
227
+ when "mysql"
228
+ return @conn.escape_string(string.to_s)
229
+ when "mysql2"
230
+ return @conn.escape(string.to_s)
231
+ when "java"
232
+ return self.escape(string)
233
+ else
234
+ raise "Unknown subtype: '#{@subtype}'."
235
+ end
236
+ end
237
+
238
+ #An alternative to the MySQL framework's escape. This is copied from the Ruby/MySQL framework at: http://www.tmtm.org/en/ruby/mysql/
239
+ def escape(string)
240
+ return string.to_s.gsub(/([\0\n\r\032\'\"\\])/) do
241
+ case $1
242
+ when "\0" then "\\0"
243
+ when "\n" then "\\n"
244
+ when "\r" then "\\r"
245
+ when "\032" then "\\Z"
246
+ else "\\#{$1}"
247
+ end
248
+ end
249
+ end
250
+
251
+ #Escapes a string to be safe to use as a column in a query.
252
+ def esc_col(string)
253
+ string = string.to_s
254
+ raise "Invalid column-string: #{string}" if string.index(@sep_col) != nil
255
+ return string
256
+ end
257
+
258
+ alias :esc_table :esc_col
259
+ alias :esc :escape
260
+
261
+ #Returns the last inserted ID for the connection.
262
+ def lastID
263
+ case @subtype
264
+ when "mysql"
265
+ @mutex.synchronize do
266
+ return @conn.insert_id.to_i
267
+ end
268
+ when "mysql2"
269
+ @mutex.synchronize do
270
+ return @conn.last_id.to_i
271
+ end
272
+ when "java"
273
+ data = self.query("SELECT LAST_INSERT_ID() AS id").fetch
274
+ return data[:id].to_i if data.key?(:id)
275
+ raise "Could not figure out last inserted ID."
276
+ end
277
+ end
278
+
279
+ #Closes the connection threadsafe.
280
+ def close
281
+ @mutex.synchronize do
282
+ @conn.close
283
+ end
284
+ end
285
+
286
+ #Destroyes the connection.
287
+ def destroy
288
+ @conn = nil
289
+ @knjdb = nil
290
+ @mutex = nil
291
+ @subtype = nil
292
+ @encoding = nil
293
+ @query_args = nil
294
+ @port = nil
295
+ end
296
+
297
+ #Inserts multiple rows in a table. Can return the inserted IDs if asked to in arguments.
298
+ def insert_multi(tablename, arr_hashes, args = nil)
299
+ sql = "INSERT INTO `#{tablename}` ("
300
+
301
+ first = true
302
+ if args and args[:keys]
303
+ keys = args[:keys]
304
+ elsif arr_hashes.first.is_a?(Hash)
305
+ keys = arr_hashes.first.keys
306
+ else
307
+ raise "Could not figure out keys."
308
+ end
309
+
310
+ keys.each do |col_name|
311
+ sql << "," if !first
312
+ first = false if first
313
+ sql << "`#{self.esc_col(col_name)}`"
314
+ end
315
+
316
+ sql << ") VALUES ("
317
+
318
+ first = true
319
+ arr_hashes.each do |hash|
320
+ if first
321
+ first = false
322
+ else
323
+ sql << "),("
324
+ end
325
+
326
+ first_key = true
327
+ if hash.is_a?(Array)
328
+ hash.each do |val|
329
+ if first_key
330
+ first_key = false
331
+ else
332
+ sql << ","
333
+ end
334
+
335
+ sql << @knjdb.sqlval(val)
336
+ end
337
+ else
338
+ hash.each do |key, val|
339
+ if first_key
340
+ first_key = false
341
+ else
342
+ sql << ","
343
+ end
344
+
345
+ sql << @knjdb.sqlval(val)
346
+ end
347
+ end
348
+ end
349
+
350
+ sql << ")"
351
+
352
+ return sql if args and args[:return_sql]
353
+
354
+ self.query(sql)
355
+
356
+ if args and args[:return_id]
357
+ first_id = self.lastID
358
+ raise "Invalid ID: #{first_id}" if first_id.to_i <= 0
359
+ ids = [first_id]
360
+ 1.upto(arr_hashes.length - 1) do |count|
361
+ ids << first_id + count
362
+ end
363
+
364
+ ids_length = ids.length
365
+ arr_hashes_length = arr_hashes.length
366
+ raise "Invalid length (#{ids_length}, #{arr_hashes_length})." if ids_length != arr_hashes_length
367
+
368
+ return ids
369
+ else
370
+ return nil
371
+ end
372
+ end
373
+
374
+ #Starts a transaction, yields the database and commits at the end.
375
+ def transaction
376
+ @knjdb.q("START TRANSACTION")
377
+
378
+ begin
379
+ yield(@knjdb)
380
+ ensure
381
+ @knjdb.q("COMMIT")
382
+ end
383
+ end
384
+ end
385
+
386
+ #This class controls the results for the normal MySQL-driver.
387
+ class Baza::Driver::Mysql_result
388
+ #Constructor. This should not be called manually.
389
+ def initialize(driver, result)
390
+ @driver = driver
391
+ @result = result
392
+ @mutex = Mutex.new
393
+
394
+ if @result
395
+ @keys = []
396
+ keys = @result.fetch_fields
397
+ keys.each do |key|
398
+ @keys << key.name.to_sym
399
+ end
400
+ end
401
+ end
402
+
403
+ #Returns a single result.
404
+ def fetch
405
+ return self.fetch_hash_symbols if @driver.knjdb.opts[:return_keys] == "symbols"
406
+ return self.fetch_hash_strings
407
+ end
408
+
409
+ #Returns a single result as a hash with strings as keys.
410
+ def fetch_hash_strings
411
+ @mutex.synchronize do
412
+ return @result.fetch_hash
413
+ end
414
+ end
415
+
416
+ #Returns a single result as a hash with symbols as keys.
417
+ def fetch_hash_symbols
418
+ fetched = nil
419
+ @mutex.synchronize do
420
+ fetched = @result.fetch_row
421
+ end
422
+
423
+ return false if !fetched
424
+
425
+ ret = {}
426
+ count = 0
427
+ @keys.each do |key|
428
+ ret[key] = fetched[count]
429
+ count += 1
430
+ end
431
+
432
+ return ret
433
+ end
434
+
435
+ #Loops over every result yielding it.
436
+ def each
437
+ while data = self.fetch_hash_symbols
438
+ yield(data)
439
+ end
440
+ end
441
+ end
442
+
443
+ #This class controls the unbuffered result for the normal MySQL-driver.
444
+ class Baza::Driver::Mysql_unbuffered_result
445
+ #Constructor. This should not be called manually.
446
+ def initialize(conn, opts, result)
447
+ @conn = conn
448
+ @result = result
449
+
450
+ if !opts.key?(:result) or opts[:result] == "hash"
451
+ @as_hash = true
452
+ elsif opts[:result] == "array"
453
+ @as_hash = false
454
+ else
455
+ raise "Unknown type of result: '#{opts[:result]}'."
456
+ end
457
+ end
458
+
459
+ #Lods the keys for the object.
460
+ def load_keys
461
+ @keys = []
462
+ keys = @res.fetch_fields
463
+ keys.each do |key|
464
+ @keys << key.name.to_sym
465
+ end
466
+ end
467
+
468
+ #Returns a single result.
469
+ def fetch
470
+ if @enum
471
+ begin
472
+ ret = @enum.next
473
+ rescue StopIteration
474
+ @enum = nil
475
+ @res = nil
476
+ end
477
+ end
478
+
479
+ if !ret and !@res and !@enum
480
+ begin
481
+ @res = @conn.use_result
482
+ @enum = @res.to_enum
483
+ ret = @enum.next
484
+ rescue Mysql::Error
485
+ #Reset it to run non-unbuffered again and then return false.
486
+ @conn.query_with_result = true
487
+ return false
488
+ rescue StopIteration
489
+ sleep 0.1
490
+ retry
491
+ end
492
+ end
493
+
494
+ if !@as_hash
495
+ return ret
496
+ else
497
+ self.load_keys if !@keys
498
+
499
+ ret_h = {}
500
+ @keys.each_index do |key_no|
501
+ ret_h[@keys[key_no]] = ret[key_no]
502
+ end
503
+
504
+ return ret_h
505
+ end
506
+ end
507
+
508
+ #Loops over every single result yielding it.
509
+ def each
510
+ while data = self.fetch
511
+ yield(data)
512
+ end
513
+ end
514
+ end
515
+
516
+ #This class controls the result for the MySQL2 driver.
517
+ class Baza::Driver::Mysql2_result
518
+ #Constructor. This should not be called manually.
519
+ def initialize(result)
520
+ @result = result
521
+ end
522
+
523
+ #Returns a single result.
524
+ def fetch
525
+ @enum = @result.to_enum if !@enum
526
+
527
+ begin
528
+ return @enum.next
529
+ rescue StopIteration
530
+ return false
531
+ end
532
+ end
533
+
534
+ #Loops over every single result yielding it.
535
+ def each
536
+ @result.each do |res|
537
+ #This sometimes happens when streaming results...
538
+ next if !res
539
+ yield(res)
540
+ end
541
+ end
542
+ end
543
+
544
+ #This class controls the result for the Java-MySQL-driver.
545
+ class KnjDB_java_mysql_result
546
+ #Constructor. This should not be called manually.
547
+ def initialize(knjdb, opts, result)
548
+ @knjdb = knjdb
549
+ @result = result
550
+
551
+ if !opts.key?(:result) or opts[:result] == "hash"
552
+ @as_hash = true
553
+ elsif opts[:result] == "array"
554
+ @as_hash = false
555
+ else
556
+ raise "Unknown type of result: '#{opts[:result]}'."
557
+ end
558
+ end
559
+
560
+ #Reads meta-data about the query like keys and count.
561
+ def read_meta
562
+ @result.before_first
563
+ meta = @result.meta_data
564
+ @count = meta.column_count
565
+
566
+ @keys = []
567
+ 1.upto(@count) do |count|
568
+ @keys << meta.column_label(count).to_sym
569
+ end
570
+ end
571
+
572
+ def fetch
573
+ return false if !@result
574
+ self.read_meta if !@keys
575
+ status = @result.next
576
+
577
+ if !status
578
+ @result = nil
579
+ @keys = nil
580
+ @count = nil
581
+ return false
582
+ end
583
+
584
+ if @as_hash
585
+ ret = {}
586
+ 1.upto(@count) do |count|
587
+ ret[@keys[count - 1]] = @result.object(count)
588
+ end
589
+ else
590
+ ret = []
591
+ 1.upto(@count) do |count|
592
+ ret << @result.object(count)
593
+ end
594
+ end
595
+
596
+ return ret
597
+ end
598
+
599
+ def each
600
+ while data = self.fetch
601
+ yield(data)
602
+ end
603
+ end
604
+ end