baza 0.0.0

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.
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