baza 0.0.14 → 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +58 -13
  3. data/VERSION +1 -1
  4. data/baza.gemspec +15 -3
  5. data/include/db.rb +871 -865
  6. data/include/drivers/mysql/mysql.rb +104 -297
  7. data/include/drivers/mysql/mysql_column.rb +133 -0
  8. data/include/drivers/mysql/mysql_columns.rb +4 -127
  9. data/include/drivers/mysql/mysql_index.rb +76 -0
  10. data/include/drivers/mysql/mysql_indexes.rb +0 -73
  11. data/include/drivers/mysql/mysql_result.rb +42 -0
  12. data/include/drivers/mysql/mysql_result_java.rb +61 -0
  13. data/include/drivers/mysql/mysql_result_mysql2.rb +26 -0
  14. data/include/drivers/mysql/mysql_result_unbuffered.rb +72 -0
  15. data/include/drivers/mysql/mysql_sqlspecs.rb +1 -1
  16. data/include/drivers/mysql/mysql_table.rb +361 -0
  17. data/include/drivers/mysql/mysql_tables.rb +23 -381
  18. data/include/drivers/sqlite3/libknjdb_java_sqlite3.rb +17 -22
  19. data/include/drivers/sqlite3/libknjdb_sqlite3_ironruby.rb +13 -13
  20. data/include/drivers/sqlite3/sqlite3.rb +39 -105
  21. data/include/drivers/sqlite3/sqlite3_column.rb +146 -0
  22. data/include/drivers/sqlite3/sqlite3_columns.rb +17 -149
  23. data/include/drivers/sqlite3/sqlite3_index.rb +55 -0
  24. data/include/drivers/sqlite3/sqlite3_indexes.rb +0 -52
  25. data/include/drivers/sqlite3/sqlite3_result.rb +35 -0
  26. data/include/drivers/sqlite3/sqlite3_result_java.rb +39 -0
  27. data/include/drivers/sqlite3/sqlite3_table.rb +399 -0
  28. data/include/drivers/sqlite3/sqlite3_tables.rb +7 -403
  29. data/include/idquery.rb +19 -19
  30. data/include/model.rb +139 -139
  31. data/include/model_handler_sqlhelper.rb +74 -74
  32. data/spec/support/driver_columns_collection.rb +17 -0
  33. metadata +14 -2
data/include/db.rb CHANGED
@@ -1,865 +1,871 @@
1
- require "knjrbfw"
2
- Knj.gem_require([:wref, :datet])
3
-
4
- #A wrapper of several possible database-types.
5
- #
6
- #===Examples
7
- # db = Baza::Db.new(:type => "mysql", :subtype => "mysql2", :db => "mysql", :user => "user", :pass => "password")
8
- # mysql_table = db.tables['mysql']
9
- # name = mysql_table.name
10
- # cols = mysql_table.columns
11
- #
12
- # db = Baza::Db.new(:type => "sqlite3", :path => "some_db.sqlite3")
13
- #
14
- # db.q("SELECT * FROM users") do |data|
15
- # print data[:name]
16
- # end
17
- class Baza::Db
18
- attr_reader :sep_col, :sep_table, :sep_val, :opts, :conn, :conns, :int_types
19
-
20
- #Returns an array containing hashes of information about each registered driver.
21
- def self.drivers
22
- path = "#{File.dirname(__FILE__)}/drivers"
23
- drivers = []
24
-
25
- Dir.foreach(path) do |file|
26
- next if file.to_s.slice(0, 1) == "."
27
- fp = "#{path}/#{file}"
28
- next unless File.directory?(fp)
29
-
30
- driver_file = "#{fp}/#{file}.rb"
31
- class_name = StringCases.snake_to_camel(file).to_sym
32
-
33
- drivers << {
34
- :name => file,
35
- :driver_path => driver_file,
36
- :class_name => class_name
37
- }
38
- end
39
-
40
- return drivers
41
- end
42
-
43
- #Tries to create a database-object based on the given object which could be a SQLite3 object or a MySQL 2 object (or other supported).
44
- def self.from_object(args)
45
- args = {:object => args} if !args.is_a?(Hash)
46
- raise "No :object was given." if !args[:object]
47
-
48
- Baza::Db.drivers.each do |driver|
49
- require driver[:driver_path]
50
-
51
- const = Baza::Driver.const_get(driver[:class_name])
52
- next unless const.respond_to?(:from_object)
53
-
54
- obj = const.from_object(args)
55
- if obj.is_a?(Hash) and obj[:type] == :success
56
- if obj[:args]
57
- new_args = obj[:args]
58
- new_args = new_args.merge(args[:new_args]) if args[:new_args]
59
- return Baza::Db.new(new_args)
60
- else
61
- raise "Didnt know what to do."
62
- end
63
- end
64
- end
65
-
66
- raise "Could not figure out what to do what object of type: '#{args[:object].class.name}'."
67
- end
68
-
69
- def initialize(opts)
70
- @conn = opts.delete(:driver) if opts[:driver]
71
- self.opts = opts if opts != nil
72
- @int_types = [:int, :bigint, :tinyint, :smallint, :mediumint]
73
-
74
- if !@opts[:threadsafe]
75
- require "monitor"
76
- @mutex = Monitor.new
77
- end
78
-
79
- @debug = @opts[:debug]
80
-
81
- self.conn_exec do |driver|
82
- @sep_table = driver.sep_table
83
- @sep_col = driver.sep_col
84
- @sep_val = driver.sep_val
85
- @esc_driver = driver
86
- end
87
- end
88
-
89
- def args
90
- return @opts
91
- end
92
-
93
- def opts=(arr_opts)
94
- @opts = {}
95
- arr_opts.each do |key, val|
96
- @opts[key.to_sym] = val
97
- end
98
-
99
- if RUBY_PLATFORM == "java"
100
- @opts[:subtype] = "java"
101
- elsif @opts[:type] == "sqlite3" and RUBY_PLATFORM.index("mswin32") != nil
102
- @opts[:subtype] = "ironruby"
103
- end
104
-
105
- @type_cc = StringCases.snake_to_camel(@opts[:type])
106
- self.connect
107
- end
108
-
109
- #Actually connects to the database. This is useually done automatically.
110
- def connect
111
- if @opts[:threadsafe]
112
- require "#{$knjpath}threadhandler"
113
- @conns = Knj::Threadhandler.new
114
-
115
- @conns.on_spawn_new do
116
- self.spawn
117
- end
118
-
119
- @conns.on_inactive do |data|
120
- data[:obj].close
121
- end
122
-
123
- @conns.on_activate do |data|
124
- data[:obj].reconnect
125
- end
126
- else
127
- @conn = self.spawn
128
- end
129
- end
130
-
131
- #Spawns a new driver (useally done automatically).
132
- #===Examples
133
- # driver_instance = db.spawn
134
- def spawn
135
- raise "No type given (#{@opts.keys.join(",")})." if !@opts[:type]
136
- rpath = "#{File.dirname(__FILE__)}/#{"drivers/#{@opts[:type]}/#{@opts[:type]}.rb"}"
137
- require rpath if (!@opts.key?(:require) or @opts[:require]) and File.exists?(rpath)
138
- return Baza::Driver.const_get(@type_cc).new(self)
139
- end
140
-
141
- #Registers a driver to the current thread.
142
- def get_and_register_thread
143
- raise "KnjDB-object is not in threadding mode." if !@conns
144
-
145
- thread_cur = Thread.current
146
- tid = self.__id__
147
- thread_cur[:baza] = {} if !thread_cur[:baza]
148
-
149
- if thread_cur[:baza][tid]
150
- #An object has already been spawned - free that first to avoid endless "used" objects.
151
- self.free_thread
152
- end
153
-
154
- thread_cur[:baza][tid] = @conns.get_and_lock if !thread_cur[:baza][tid]
155
-
156
- #If block given then be ensure to free thread after yielding.
157
- if block_given?
158
- begin
159
- yield
160
- ensure
161
- self.free_thread
162
- end
163
- end
164
- end
165
-
166
- #Frees the current driver from the current thread.
167
- def free_thread
168
- thread_cur = Thread.current
169
- tid = self.__id__
170
-
171
- if thread_cur[:baza] and thread_cur[:baza].key?(tid)
172
- db = thread_cur[:baza][tid]
173
- thread_cur[:baza].delete(tid)
174
- @conns.free(db) if @conns
175
- end
176
- end
177
-
178
- #Clean up various memory-stuff if possible.
179
- def clean
180
- if @conns
181
- @conns.objects.each do |data|
182
- data[:object].clean if data[:object].respond_to?("clean")
183
- end
184
- elsif @conn
185
- @conn.clean if @conn.respond_to?("clean")
186
- end
187
- end
188
-
189
- #The all driver-database-connections.
190
- def close
191
- @conn.close if @conn
192
- @conns.destroy if @conns
193
-
194
- @conn = nil
195
- @conns = nil
196
- end
197
-
198
- #Clones the current database-connection with possible extra arguments.
199
- def clone_conn(args = {})
200
- conn = Baza::Db.new(opts = @opts.clone.merge(args))
201
-
202
- if block_given?
203
- begin
204
- yield(conn)
205
- ensure
206
- conn.close
207
- end
208
-
209
- return nil
210
- else
211
- return conn
212
- end
213
- end
214
-
215
- COPY_TO_ALLOWED_ARGS = [:tables, :debug]
216
- #Copies the content of the current database to another instance of Baza::Db.
217
- def copy_to(db, args = {})
218
- debug = args[:debug]
219
- raise "No tables given." if !data[:tables]
220
-
221
- data[:tables].each do |table|
222
- table_args = nil
223
- table_args = args[:tables][table[:name.to_sym]] if args and args[:tables] and args[:tables][table[:name].to_sym]
224
- next if table_args and table_args[:skip]
225
- table.delete(:indexes) if table.key?(:indexes) and args[:skip_indexes]
226
-
227
- table_name = table.delete(:name)
228
- puts "Creating table: '#{table_name}'." if debug
229
- db.tables.create(table_name, table)
230
-
231
- limit_from = 0
232
- limit_incr = 1000
233
-
234
- loop do
235
- puts "Copying rows (#{limit_from}, #{limit_incr})." if debug
236
- ins_arr = []
237
- q_rows = self.select(table_name, {}, {:limit_from => limit_from, :limit_to => limit_incr})
238
- while d_rows = q_rows.fetch
239
- col_args = nil
240
-
241
- if table_args and table_args[:columns]
242
- d_rows.each do |col_name, col_data|
243
- col_args = table_args[:columns][col_name.to_sym] if table_args and table_args[:columns]
244
- d_rows[col_name] = "" if col_args and col_args[:empty]
245
- end
246
- end
247
-
248
- ins_arr << d_rows
249
- end
250
-
251
- break if ins_arr.empty?
252
-
253
- puts "Insertering #{ins_arr.length} rows." if debug
254
- db.insert_multi(table_name, ins_arr)
255
- limit_from += limit_incr
256
- end
257
- end
258
- end
259
-
260
- #Returns the data of this database in a hash.
261
- #===Examples
262
- # data = db.data
263
- # tables_hash = data['tables']
264
- def data
265
- tables_ret = []
266
- tables.list.each do |name, table|
267
- tables_ret << table.data
268
- end
269
-
270
- return {
271
- :tables => tables_ret
272
- }
273
- end
274
-
275
- #Simply inserts data into a table.
276
- #
277
- #===Examples
278
- # db.insert(:users, {:name => "John", :lastname => "Doe"})
279
- # id = db.insert(:users, {:name => "John", :lastname => "Doe"}, :return_id => true)
280
- # sql = db.insert(:users, {:name => "John", :lastname => "Doe"}, :return_sql => true) #=> "INSERT INTO `users` (`name`, `lastname`) VALUES ('John', 'Doe')"
281
- def insert(tablename, arr_insert, args = nil)
282
- sql = "INSERT INTO #{@sep_table}#{self.esc_table(tablename)}#{@sep_table}"
283
-
284
- if !arr_insert or arr_insert.empty?
285
- #This is the correct syntax for inserting a blank row in MySQL.
286
- if @opts[:type].to_s == "mysql"
287
- sql << " VALUES ()"
288
- elsif @opts[:type].to_s == "sqlite3"
289
- sql << " DEFAULT VALUES"
290
- else
291
- raise "Unknown database-type: '#{@opts[:type]}'."
292
- end
293
- else
294
- sql << " ("
295
-
296
- first = true
297
- arr_insert.each do |key, value|
298
- if first
299
- first = false
300
- else
301
- sql << ", "
302
- end
303
-
304
- sql << "#{@sep_col}#{self.esc_col(key)}#{@sep_col}"
305
- end
306
-
307
- sql << ") VALUES ("
308
-
309
- first = true
310
- arr_insert.each do |key, value|
311
- if first
312
- first = false
313
- else
314
- sql << ", "
315
- end
316
-
317
- sql << self.sqlval(value)
318
- end
319
-
320
- sql << ")"
321
- end
322
-
323
- return sql if args and args[:return_sql]
324
-
325
- self.conn_exec do |driver|
326
- begin
327
- driver.query(sql)
328
- rescue => e
329
- self.add_sql_to_error(e, sql) if @opts[:sql_to_error]
330
- raise e
331
- end
332
-
333
- return driver.lastID if args and args[:return_id]
334
- return nil
335
- end
336
- end
337
-
338
- def add_sql_to_error(error, sql)
339
- error.message << " (SQL: #{sql})"
340
- end
341
-
342
- #Returns the correct SQL-value for the given value. If it is a number, then just the raw number as a string will be returned. nil's will be NULL and strings will have quotes and will be escaped.
343
- def sqlval(val)
344
- return @conn.sqlval(val) if @conn.respond_to?(:sqlval)
345
-
346
- if val.is_a?(Fixnum) or val.is_a?(Integer)
347
- return val.to_s
348
- elsif val == nil
349
- return "NULL"
350
- elsif val.is_a?(Date)
351
- return "#{@sep_val}#{Datet.in(val).dbstr(:time => false)}#{@sep_val}"
352
- elsif val.is_a?(Time) or val.is_a?(DateTime)
353
- return "#{@sep_val}#{Datet.in(val).dbstr}#{@sep_val}"
354
- else
355
- return "#{@sep_val}#{self.escape(val)}#{@sep_val}"
356
- end
357
- end
358
-
359
- #Simply and optimal insert multiple rows into a table in a single query. Uses the drivers functionality if supported or inserts each row manually.
360
- #
361
- #===Examples
362
- # db.insert_multi(:users, [
363
- # {:name => "John", :lastname => "Doe"},
364
- # {:name => "Kasper", :lastname => "Johansen"}
365
- # ])
366
- def insert_multi(tablename, arr_hashes, args = nil)
367
- return false if arr_hashes.empty?
368
-
369
- if @esc_driver.respond_to?(:insert_multi)
370
- if args and args[:return_sql]
371
- res = @esc_driver.insert_multi(tablename, arr_hashes, args)
372
- if res.is_a?(String)
373
- return [res]
374
- elsif res.is_a?(Array)
375
- return res
376
- else
377
- raise "Unknown result: '#{res.class.name}'."
378
- end
379
- end
380
-
381
- self.conn_exec do |driver|
382
- return driver.insert_multi(tablename, arr_hashes, args)
383
- end
384
- else
385
- transaction do
386
- arr_hashes.each do |hash|
387
- self.insert(tablename, hash, args)
388
- end
389
- end
390
-
391
- return nil
392
- end
393
- end
394
-
395
- #Simple updates rows.
396
- #
397
- #===Examples
398
- # db.update(:users, {:name => "John"}, {:lastname => "Doe"})
399
- def update(tablename, hash_update, arr_terms = {}, args = nil)
400
- raise "'hash_update' was not a hash: '#{hash_update.class.name}'." if !hash_update.is_a?(Hash)
401
- return false if hash_update.empty?
402
-
403
- sql = ""
404
- sql << "UPDATE #{@sep_col}#{tablename}#{@sep_col} SET "
405
-
406
- first = true
407
- hash_update.each do |key, value|
408
- if first
409
- first = false
410
- else
411
- sql << ", "
412
- end
413
-
414
- #Convert dates to valid dbstr.
415
- value = self.date_out(value) if value.is_a?(Datet) or value.is_a?(Time)
416
-
417
- sql << "#{@sep_col}#{self.esc_col(key)}#{@sep_col} = "
418
- sql << self.sqlval(value)
419
- end
420
-
421
- if arr_terms and arr_terms.length > 0
422
- sql << " WHERE #{self.makeWhere(arr_terms)}"
423
- end
424
-
425
- return sql if args and args[:return_sql]
426
-
427
- self.conn_exec do |driver|
428
- driver.query(sql)
429
- end
430
- end
431
-
432
- #Checks if a given terms exists. If it does, updates it to match data. If not inserts the row.
433
- def upsert(table, data, terms, args = nil)
434
- row = self.select(table, terms, "limit" => 1).fetch
435
-
436
- if args and args[:buffer]
437
- obj = args[:buffer]
438
- else
439
- obj = self
440
- end
441
-
442
- if row
443
- obj.update(table, data, terms)
444
- else
445
- obj.insert(table, terms.merge(data))
446
- end
447
- end
448
-
449
- SELECT_ARGS_ALLOWED_KEYS = [:limit, :limit_from, :limit_to]
450
- #Makes a select from the given arguments: table-name, where-terms and other arguments as limits and orders. Also takes a block to avoid raping of memory.
451
- def select(tablename, arr_terms = nil, args = nil, &block)
452
- #Set up vars.
453
- sql = ""
454
- args_q = nil
455
- select_sql = "*"
456
-
457
- #Give 'cloned_ubuf' argument to 'q'-method.
458
- if args and args[:cloned_ubuf]
459
- args_q = {:cloned_ubuf => true}
460
- end
461
-
462
- #Set up IDQuery-stuff if that is given in arguments.
463
- if args and args[:idquery]
464
- if args[:idquery] == true
465
- select_sql = "`id`"
466
- col = :id
467
- else
468
- select_sql = "`#{self.esc_col(args[:idquery])}`"
469
- col = args[:idquery]
470
- end
471
- end
472
-
473
- sql = "SELECT #{select_sql} FROM #{@sep_table}#{tablename}#{@sep_table}"
474
-
475
- if arr_terms != nil and !arr_terms.empty?
476
- sql << " WHERE #{self.makeWhere(arr_terms)}"
477
- end
478
-
479
- if args != nil
480
- if args[:orderby]
481
- sql << " ORDER BY #{args[:orderby]}"
482
- end
483
-
484
- if args[:limit]
485
- sql << " LIMIT #{args[:limit]}"
486
- end
487
-
488
- if args[:limit_from] and args[:limit_to]
489
- raise "'limit_from' was not numeric: '#{args[:limit_from]}'." if !(Float(args[:limit_from]) rescue false)
490
- raise "'limit_to' was not numeric: '#{args[:limit_to]}'." if !(Float(args[:limit_to]) rescue false)
491
- sql << " LIMIT #{args[:limit_from]}, #{args[:limit_to]}"
492
- end
493
- end
494
-
495
- #Do IDQuery if given in arguments.
496
- if args and args[:idquery]
497
- res = Baza::Idquery.new(:db => self, :table => tablename, :query => sql, :col => col, &block)
498
- else
499
- res = self.q(sql, args_q, &block)
500
- end
501
-
502
- #Return result if a block wasnt given.
503
- if block
504
- return nil
505
- else
506
- return res
507
- end
508
- end
509
-
510
- #Returns a single row from a database.
511
- #
512
- #===Examples
513
- # row = db.single(:users, {:lastname => "Doe"})
514
- def single(tablename, arr_terms = nil, args = {})
515
- args[:limit] = 1
516
-
517
- #Experienced very weird memory leak if this was not done by block. Maybe bug in Ruby 1.9.2? - knj
518
- self.select(tablename, arr_terms, args) do |data|
519
- return data
520
- end
521
-
522
- return false
523
- end
524
-
525
- alias :selectsingle :single
526
-
527
- #Deletes rows from the database.
528
- #
529
- #===Examples
530
- # db.delete(:users, {:lastname => "Doe"})
531
- def delete(tablename, arr_terms, args = nil)
532
- sql = "DELETE FROM #{@sep_table}#{tablename}#{@sep_table}"
533
-
534
- if arr_terms != nil and !arr_terms.empty?
535
- sql << " WHERE #{self.makeWhere(arr_terms)}"
536
- end
537
-
538
- return sql if args and args[:return_sql]
539
-
540
- self.conn_exec do |driver|
541
- driver.query(sql)
542
- end
543
-
544
- return nil
545
- end
546
-
547
- #Internally used to generate SQL.
548
- #
549
- #===Examples
550
- # sql = db.makeWhere({:lastname => "Doe"}, driver_obj)
551
- def makeWhere(arr_terms, driver = nil)
552
- sql = ""
553
-
554
- first = true
555
- arr_terms.each do |key, value|
556
- if first
557
- first = false
558
- else
559
- sql << " AND "
560
- end
561
-
562
- if value.is_a?(Array)
563
- raise "Array for column '#{key}' was empty." if value.empty?
564
- sql << "#{@sep_col}#{key}#{@sep_col} IN (#{Knj::ArrayExt.join(:arr => value, :sep => ",", :surr => "'", :callback => proc{|ele| self.esc(ele)})})"
565
- elsif value.is_a?(Hash)
566
- raise "Dont know how to handle hash."
567
- else
568
- sql << "#{@sep_col}#{key}#{@sep_col} = #{self.sqlval(value)}"
569
- end
570
- end
571
-
572
- return sql
573
- end
574
-
575
- #Returns a driver-object based on the current thread and free driver-objects.
576
- #
577
- #===Examples
578
- # db.conn_exec do |driver|
579
- # str = driver.escape('something̈́')
580
- # end
581
- def conn_exec
582
- if tcur = Thread.current and tcur[:baza]
583
- tid = self.__id__
584
-
585
- if tcur[:baza].key?(tid)
586
- yield(tcur[:baza][tid])
587
- return nil
588
- end
589
- end
590
-
591
- if @conns
592
- conn = @conns.get_and_lock
593
-
594
- begin
595
- yield(conn)
596
- return nil
597
- ensure
598
- @conns.free(conn)
599
- end
600
- elsif @conn
601
- @mutex.synchronize do
602
- yield(@conn)
603
- return nil
604
- end
605
- end
606
-
607
- raise "Could not figure out which driver to use?"
608
- end
609
-
610
- #Executes a query and returns the result.
611
- #
612
- #===Examples
613
- # res = db.query('SELECT * FROM users')
614
- # while data = res.fetch
615
- # print data[:name]
616
- # end
617
- def query(string)
618
- if @debug
619
- print "SQL: #{string}\n"
620
-
621
- if @debug.is_a?(Fixnum) and @debug >= 2
622
- print caller.join("\n")
623
- print "\n"
624
- end
625
- end
626
-
627
- begin
628
- self.conn_exec do |driver|
629
- return driver.query(string)
630
- end
631
- rescue => e
632
- e.message << " (SQL: #{string})" if @opts[:sql_to_error]
633
- raise e
634
- end
635
- end
636
-
637
- #Execute an ubuffered query and returns the result.
638
- #
639
- #===Examples
640
- # db.query_ubuf('SELECT * FROM users') do |data|
641
- # print data[:name]
642
- # end
643
- def query_ubuf(string, &block)
644
- ret = nil
645
-
646
- self.conn_exec do |driver|
647
- ret = driver.query_ubuf(string, &block)
648
- end
649
-
650
- if block
651
- ret.each(&block)
652
- return nil
653
- end
654
-
655
- return ret
656
- end
657
-
658
- #Clones the connection, executes the given block and closes the connection again.
659
- #
660
- #===Examples
661
- # db.cloned_conn do |conn|
662
- # conn.q('SELCET * FROM users') do |data|
663
- # print data[:name]
664
- # end
665
- # end
666
- def cloned_conn(args = nil, &block)
667
- clone_conn_args = {
668
- :threadsafe => false
669
- }
670
-
671
- clone_conn_args.merge!(args[:clone_args]) if args and args[:clone_args]
672
- dbconn = self.clone_conn(clone_conn_args)
673
-
674
- begin
675
- yield(dbconn)
676
- ensure
677
- dbconn.close
678
- end
679
- end
680
-
681
- #Executes a query and returns the result. If a block is given the result is iterated over that block instead and it returns nil.
682
- #
683
- #===Examples
684
- # db.q('SELECT * FROM users') do |data|
685
- # print data[:name]
686
- # end
687
- def q(str, args = nil, &block)
688
- #If the query should be executed in a new connection unbuffered.
689
- if args
690
- if args[:cloned_ubuf]
691
- raise "No block given." if !block
692
-
693
- self.cloned_conn(:clone_args => args[:clone_args]) do |cloned_conn|
694
- ret = cloned_conn.query_ubuf(str)
695
- ret.each(&block)
696
- end
697
-
698
- return nil
699
- else
700
- raise "Invalid arguments given: '#{args}'."
701
- end
702
- end
703
-
704
- ret = self.query(str)
705
-
706
- if block
707
- ret.each(&block)
708
- return nil
709
- end
710
-
711
- return ret
712
- end
713
-
714
- #Yields a query-buffer and flushes at the end of the block given.
715
- def q_buffer(args = {}, &block)
716
- Baza::QueryBuffer.new(args.merge(:db => self), &block)
717
- return nil
718
- end
719
-
720
- #Returns the last inserted ID.
721
- #
722
- #===Examples
723
- # id = db.last_id
724
- def lastID
725
- self.conn_exec do |driver|
726
- return driver.lastID
727
- end
728
- end
729
-
730
- alias :last_id :lastID
731
-
732
- #Escapes a string to be safe-to-use in a query-string.
733
- #
734
- #===Examples
735
- # db.q("INSERT INTO users (name) VALUES ('#{db.esc('John')}')")
736
- def escape(string)
737
- return @esc_driver.escape(string)
738
- end
739
-
740
- alias :esc :escape
741
-
742
- #Escapes the given string to be used as a column.
743
- def esc_col(str)
744
- return @esc_driver.esc_col(str)
745
- end
746
-
747
- #Escapes the given string to be used as a table.
748
- def esc_table(str)
749
- return @esc_driver.esc_table(str)
750
- end
751
-
752
- #Returns a string which can be used in SQL with the current driver.
753
- #===Examples
754
- # str = db.date_out(Time.now) #=> "2012-05-20 22:06:09"
755
- def date_out(date_obj = Datet.new, args = {})
756
- if @esc_driver.respond_to?(:date_out)
757
- return @esc_driver.date_out(date_obj, args)
758
- end
759
-
760
- return Datet.in(date_obj).dbstr(args)
761
- end
762
-
763
- #Takes a valid date-db-string and converts it into a Datet.
764
- #===Examples
765
- # db.date_in('2012-05-20 22:06:09') #=> 2012-05-20 22:06:09 +0200
766
- def date_in(date_obj)
767
- if @esc_driver.respond_to?(:date_in)
768
- return @esc_driver.date_in(date_obj)
769
- end
770
-
771
- return Datet.in(date_obj)
772
- end
773
-
774
- #Returns the table-module and spawns it if it isnt already spawned.
775
- def tables
776
- if !@tables
777
- require "#{File.dirname(__FILE__)}/drivers/#{@opts[:type]}/#{@opts[:type]}_tables" if (!@opts.key?(:require) or @opts[:require]) unless Baza::Driver.const_get(@type_cc).const_defined?(:Tables)
778
- @tables = Baza::Driver.const_get(@type_cc).const_get(:Tables).new(
779
- :db => self
780
- )
781
- end
782
-
783
- return @tables
784
- end
785
-
786
- #Returns the columns-module and spawns it if it isnt already spawned.
787
- def cols
788
- if !@cols
789
- require "#{File.dirname(__FILE__)}/drivers/#{@opts[:type]}/#{@opts[:type]}_columns" if (!@opts.key?(:require) or @opts[:require]) unless Baza::Driver.const_get(@type_cc).const_defined?(:Columns)
790
- @cols = Baza::Driver.const_get(@type_cc).const_get(:Columns).new(
791
- :db => self
792
- )
793
- end
794
-
795
- return @cols
796
- end
797
-
798
- #Returns the index-module and spawns it if it isnt already spawned.
799
- def indexes
800
- if !@indexes
801
- require "#{File.dirname(__FILE__)}/drivers/#{@opts[:type]}/#{@opts[:type]}_indexes" if (!@opts.key?(:require) or @opts[:require]) unless Baza::Driver.const_get(@type_cc).const_defined?(:Indexes)
802
- @indexes = Baza::Driver.const_get(@type_cc).const_get(:Indexes).new(
803
- :db => self
804
- )
805
- end
806
-
807
- return @indexes
808
- end
809
-
810
- #Returns the SQLSpec-module and spawns it if it isnt already spawned.
811
- def sqlspecs
812
- if !@sqlspecs
813
- require "#{File.dirname(__FILE__)}/drivers/#{@opts[:type]}/#{@opts[:type]}_sqlspecs" if (!@opts.key?(:require) or @opts[:require]) unless Baza::Driver.const_get(@type_cc).const_defined?(:Sqlspecs)
814
- @sqlspecs = Baza::Driver.const_get(@type_cc).const_get(:Sqlspecs).new(
815
- :db => self
816
- )
817
- end
818
-
819
- return @sqlspecs
820
- end
821
-
822
- #Beings a transaction and commits when the block ends.
823
- #
824
- #===Examples
825
- # db.transaction do |db|
826
- # db.insert(:users, name: "John")
827
- # db.insert(:users, name: "Kasper")
828
- # end
829
- def transaction(&block)
830
- self.conn_exec do |driver|
831
- driver.transaction(&block)
832
- end
833
-
834
- return nil
835
- end
836
-
837
- #Optimizes all tables in the database.
838
- def optimize(args = nil)
839
- STDOUT.puts "Beginning optimization of database." if @debug or (args and args[:debug])
840
- self.tables.list do |table|
841
- STDOUT.puts "Optimizing table: '#{table.name}'." if @debug or (args and args[:debug])
842
- table.optimize
843
- end
844
-
845
- return nil
846
- end
847
-
848
- #Proxies the method to the driver.
849
- #
850
- #===Examples
851
- # db.method_on_driver
852
- def method_missing(method_name, *args)
853
- self.conn_exec do |driver|
854
- if driver.respond_to?(method_name.to_sym)
855
- return driver.send(method_name, *args)
856
- end
857
- end
858
-
859
- raise "Method not found: '#{method_name}'."
860
- end
861
-
862
- def to_s
863
- "#<Baza::Db driver \"#{@opts[:type]}\">"
864
- end
865
- end
1
+ require "knjrbfw"
2
+ Knj.gem_require([:wref, :datet])
3
+
4
+ #A wrapper of several possible database-types.
5
+ #
6
+ #===Examples
7
+ # db = Baza::Db.new(type: "mysql", subtype: "mysql2", db: "mysql", user: "user", pass: "password")
8
+ # mysql_table = db.tables['mysql']
9
+ # name = mysql_table.name
10
+ # cols = mysql_table.columns
11
+ #
12
+ # db = Baza::Db.new(type: "sqlite3", path: "some_db.sqlite3")
13
+ #
14
+ # db.q("SELECT * FROM users") do |data|
15
+ # print data[:name]
16
+ # end
17
+ class Baza::Db
18
+ attr_reader :sep_col, :sep_table, :sep_val, :opts, :conn, :conns, :int_types
19
+
20
+ #Returns an array containing hashes of information about each registered driver.
21
+ def self.drivers
22
+ path = "#{File.dirname(__FILE__)}/drivers"
23
+ drivers = []
24
+
25
+ Dir.foreach(path) do |file|
26
+ next if file.to_s.slice(0, 1) == "."
27
+ fp = "#{path}/#{file}"
28
+ next unless File.directory?(fp)
29
+
30
+ driver_file = "#{fp}/#{file}.rb"
31
+ class_name = StringCases.snake_to_camel(file).to_sym
32
+
33
+ drivers << {
34
+ name: file,
35
+ driver_path: driver_file,
36
+ class_name: class_name
37
+ }
38
+ end
39
+
40
+ return drivers
41
+ end
42
+
43
+ #Tries to create a database-object based on the given object which could be a SQLite3 object or a MySQL 2 object (or other supported).
44
+ def self.from_object(args)
45
+ args = {object: args} unless args.is_a?(Hash)
46
+ raise "No :object was given." unless args[:object]
47
+
48
+ Baza::Db.drivers.each do |driver|
49
+ require driver[:driver_path]
50
+
51
+ const = Baza::Driver.const_get(driver[:class_name])
52
+ next unless const.respond_to?(:from_object)
53
+
54
+ obj = const.from_object(args)
55
+ if obj.is_a?(Hash) && obj[:type] == :success
56
+ if obj[:args]
57
+ new_args = obj[:args]
58
+ new_args = new_args.merge(args[:new_args]) if args[:new_args]
59
+ return Baza::Db.new(new_args)
60
+ else
61
+ raise "Didnt know what to do."
62
+ end
63
+ end
64
+ end
65
+
66
+ raise "Could not figure out what to do what object of type: '#{args[:object].class.name}'."
67
+ end
68
+
69
+ def initialize(opts)
70
+ @conn = opts.delete(:driver) if opts[:driver]
71
+ self.opts = opts if opts != nil
72
+ @int_types = [:int, :bigint, :tinyint, :smallint, :mediumint]
73
+
74
+ if !@opts[:threadsafe]
75
+ require "monitor"
76
+ @mutex = Monitor.new
77
+ end
78
+
79
+ @debug = @opts[:debug]
80
+
81
+ self.conn_exec do |driver|
82
+ @sep_table = driver.sep_table
83
+ @sep_col = driver.sep_col
84
+ @sep_val = driver.sep_val
85
+ @esc_driver = driver
86
+ end
87
+ end
88
+
89
+ def args
90
+ return @opts
91
+ end
92
+
93
+ def opts=(arr_opts)
94
+ @opts = {}
95
+ arr_opts.each do |key, val|
96
+ @opts[key.to_sym] = val
97
+ end
98
+
99
+ if RUBY_PLATFORM == "java"
100
+ @opts[:subtype] = "java"
101
+ elsif @opts[:type] == "sqlite3" && RUBY_PLATFORM.index("mswin32") != nil
102
+ @opts[:subtype] = "ironruby"
103
+ end
104
+
105
+ @type_cc = StringCases.snake_to_camel(@opts[:type])
106
+ self.connect
107
+ end
108
+
109
+ #Actually connects to the database. This is useually done automatically.
110
+ def connect
111
+ if @opts[:threadsafe]
112
+ require "#{$knjpath}threadhandler"
113
+ @conns = Knj::Threadhandler.new
114
+
115
+ @conns.on_spawn_new do
116
+ self.spawn
117
+ end
118
+
119
+ @conns.on_inactive do |data|
120
+ data[:obj].close
121
+ end
122
+
123
+ @conns.on_activate do |data|
124
+ data[:obj].reconnect
125
+ end
126
+ else
127
+ @conn = self.spawn
128
+ end
129
+ end
130
+
131
+ #Spawns a new driver (useally done automatically).
132
+ #===Examples
133
+ # driver_instance = db.spawn
134
+ def spawn
135
+ raise "No type given (#{@opts.keys.join(",")})." unless @opts[:type]
136
+ rpath = "#{File.dirname(__FILE__)}/#{"drivers/#{@opts[:type]}/#{@opts[:type]}.rb"}"
137
+ require rpath if (!@opts.key?(:require) || @opts[:require]) && File.exists?(rpath)
138
+ return Baza::Driver.const_get(@type_cc).new(self)
139
+ end
140
+
141
+ #Registers a driver to the current thread.
142
+ def get_and_register_thread
143
+ raise "KnjDB-object is not in threadding mode." unless @conns
144
+
145
+ thread_cur = Thread.current
146
+ tid = self.__id__
147
+ thread_cur[:baza] = {} if !thread_cur[:baza]
148
+
149
+ if thread_cur[:baza][tid]
150
+ #An object has already been spawned - free that first to avoid endless "used" objects.
151
+ self.free_thread
152
+ end
153
+
154
+ thread_cur[:baza][tid] = @conns.get_and_lock unless thread_cur[:baza][tid]
155
+
156
+ #If block given then be ensure to free thread after yielding.
157
+ if block_given?
158
+ begin
159
+ yield
160
+ ensure
161
+ self.free_thread
162
+ end
163
+ end
164
+ end
165
+
166
+ #Frees the current driver from the current thread.
167
+ def free_thread
168
+ thread_cur = Thread.current
169
+ tid = self.__id__
170
+
171
+ if thread_cur[:baza] && thread_cur[:baza].key?(tid)
172
+ db = thread_cur[:baza][tid]
173
+ thread_cur[:baza].delete(tid)
174
+ @conns.free(db) if @conns
175
+ end
176
+ end
177
+
178
+ #Clean up various memory-stuff if possible.
179
+ def clean
180
+ if @conns
181
+ @conns.objects.each do |data|
182
+ data[:object].clean if data[:object].respond_to?("clean")
183
+ end
184
+ elsif @conn
185
+ @conn.clean if @conn.respond_to?("clean")
186
+ end
187
+ end
188
+
189
+ #The all driver-database-connections.
190
+ def close
191
+ @conn.close if @conn
192
+ @conns.destroy if @conns
193
+
194
+ @conn = nil
195
+ @conns = nil
196
+
197
+ @closed = true
198
+ end
199
+
200
+ def closed?
201
+ @closed
202
+ end
203
+
204
+ #Clones the current database-connection with possible extra arguments.
205
+ def clone_conn(args = {})
206
+ conn = Baza::Db.new(opts = @opts.clone.merge(args))
207
+
208
+ if block_given?
209
+ begin
210
+ yield(conn)
211
+ ensure
212
+ conn.close
213
+ end
214
+
215
+ return nil
216
+ else
217
+ return conn
218
+ end
219
+ end
220
+
221
+ COPY_TO_ALLOWED_ARGS = [:tables, :debug]
222
+ #Copies the content of the current database to another instance of Baza::Db.
223
+ def copy_to(db, args = {})
224
+ debug = args[:debug]
225
+ raise "No tables given." if !data[:tables]
226
+
227
+ data[:tables].each do |table|
228
+ table_args = nil
229
+ table_args = args[:tables][table[:name.to_sym]] if args && args[:tables] && args[:tables][table[:name].to_sym]
230
+ next if table_args && table_args[:skip]
231
+ table.delete(:indexes) if table.key?(:indexes) && args[:skip_indexes]
232
+
233
+ table_name = table.delete(:name)
234
+ puts "Creating table: '#{table_name}'." if debug
235
+ db.tables.create(table_name, table)
236
+
237
+ limit_from = 0
238
+ limit_incr = 1000
239
+
240
+ loop do
241
+ puts "Copying rows (#{limit_from}, #{limit_incr})." if debug
242
+ ins_arr = []
243
+ q_rows = self.select(table_name, {}, {limit_from: limit_from, limit_to: limit_incr})
244
+ while d_rows = q_rows.fetch
245
+ col_args = nil
246
+
247
+ if table_args && table_args[:columns]
248
+ d_rows.each do |col_name, col_data|
249
+ col_args = table_args[:columns][col_name.to_sym] if table_args && table_args[:columns]
250
+ d_rows[col_name] = "" if col_args && col_args[:empty]
251
+ end
252
+ end
253
+
254
+ ins_arr << d_rows
255
+ end
256
+
257
+ break if ins_arr.empty?
258
+
259
+ puts "Insertering #{ins_arr.length} rows." if debug
260
+ db.insert_multi(table_name, ins_arr)
261
+ limit_from += limit_incr
262
+ end
263
+ end
264
+ end
265
+
266
+ #Returns the data of this database in a hash.
267
+ #===Examples
268
+ # data = db.data
269
+ # tables_hash = data['tables']
270
+ def data
271
+ tables_ret = []
272
+ tables.list.each do |name, table|
273
+ tables_ret << table.data
274
+ end
275
+
276
+ return {
277
+ tables: tables_ret
278
+ }
279
+ end
280
+
281
+ #Simply inserts data into a table.
282
+ #
283
+ #===Examples
284
+ # db.insert(:users, name: "John", lastname: "Doe")
285
+ # id = db.insert(:users, {name: "John", lastname: "Doe"}, return_id: true)
286
+ # sql = db.insert(:users, {name: "John", lastname: "Doe"}, return_sql: true) #=> "INSERT INTO `users` (`name`, `lastname`) VALUES ('John', 'Doe')"
287
+ def insert(tablename, arr_insert, args = nil)
288
+ sql = "INSERT INTO #{@sep_table}#{self.esc_table(tablename)}#{@sep_table}"
289
+
290
+ if !arr_insert || arr_insert.empty?
291
+ #This is the correct syntax for inserting a blank row in MySQL.
292
+ if @opts[:type].to_s == "mysql"
293
+ sql << " VALUES ()"
294
+ elsif @opts[:type].to_s == "sqlite3"
295
+ sql << " DEFAULT VALUES"
296
+ else
297
+ raise "Unknown database-type: '#{@opts[:type]}'."
298
+ end
299
+ else
300
+ sql << " ("
301
+
302
+ first = true
303
+ arr_insert.each do |key, value|
304
+ if first
305
+ first = false
306
+ else
307
+ sql << ", "
308
+ end
309
+
310
+ sql << "#{@sep_col}#{self.esc_col(key)}#{@sep_col}"
311
+ end
312
+
313
+ sql << ") VALUES ("
314
+
315
+ first = true
316
+ arr_insert.each do |key, value|
317
+ if first
318
+ first = false
319
+ else
320
+ sql << ", "
321
+ end
322
+
323
+ sql << self.sqlval(value)
324
+ end
325
+
326
+ sql << ")"
327
+ end
328
+
329
+ return sql if args && args[:return_sql]
330
+
331
+ self.conn_exec do |driver|
332
+ begin
333
+ driver.query(sql)
334
+ rescue => e
335
+ self.add_sql_to_error(e, sql) if @opts[:sql_to_error]
336
+ raise e
337
+ end
338
+
339
+ return driver.last_id if args && args[:return_id]
340
+ return nil
341
+ end
342
+ end
343
+
344
+ def add_sql_to_error(error, sql)
345
+ error.message << " (SQL: #{sql})"
346
+ end
347
+
348
+ #Returns the correct SQL-value for the given value. If it is a number, then just the raw number as a string will be returned. nil's will be NULL and strings will have quotes and will be escaped.
349
+ def sqlval(val)
350
+ return @conn.sqlval(val) if @conn.respond_to?(:sqlval)
351
+
352
+ if val.is_a?(Fixnum) || val.is_a?(Integer)
353
+ return val.to_s
354
+ elsif val == nil
355
+ return "NULL"
356
+ elsif val.is_a?(Date)
357
+ return "#{@sep_val}#{Datet.in(val).dbstr(time: false)}#{@sep_val}"
358
+ elsif val.is_a?(Time) || val.is_a?(DateTime)
359
+ return "#{@sep_val}#{Datet.in(val).dbstr}#{@sep_val}"
360
+ else
361
+ return "#{@sep_val}#{self.escape(val)}#{@sep_val}"
362
+ end
363
+ end
364
+
365
+ #Simply and optimal insert multiple rows into a table in a single query. Uses the drivers functionality if supported or inserts each row manually.
366
+ #
367
+ #===Examples
368
+ # db.insert_multi(:users, [
369
+ # {name: "John", lastname: "Doe"},
370
+ # {name: "Kasper", lastname: "Johansen"}
371
+ # ])
372
+ def insert_multi(tablename, arr_hashes, args = nil)
373
+ return false if arr_hashes.empty?
374
+
375
+ if @esc_driver.respond_to?(:insert_multi)
376
+ if args && args[:return_sql]
377
+ res = @esc_driver.insert_multi(tablename, arr_hashes, args)
378
+ if res.is_a?(String)
379
+ return [res]
380
+ elsif res.is_a?(Array)
381
+ return res
382
+ else
383
+ raise "Unknown result: '#{res.class.name}'."
384
+ end
385
+ end
386
+
387
+ self.conn_exec do |driver|
388
+ return driver.insert_multi(tablename, arr_hashes, args)
389
+ end
390
+ else
391
+ transaction do
392
+ arr_hashes.each do |hash|
393
+ self.insert(tablename, hash, args)
394
+ end
395
+ end
396
+
397
+ return nil
398
+ end
399
+ end
400
+
401
+ #Simple updates rows.
402
+ #
403
+ #===Examples
404
+ # db.update(:users, {name: "John"}, {lastname: "Doe"})
405
+ def update(tablename, hash_update, arr_terms = {}, args = nil)
406
+ raise "'hash_update' was not a hash: '#{hash_update.class.name}'." if !hash_update.is_a?(Hash)
407
+ return false if hash_update.empty?
408
+
409
+ sql = ""
410
+ sql << "UPDATE #{@sep_col}#{tablename}#{@sep_col} SET "
411
+
412
+ first = true
413
+ hash_update.each do |key, value|
414
+ if first
415
+ first = false
416
+ else
417
+ sql << ", "
418
+ end
419
+
420
+ #Convert dates to valid dbstr.
421
+ value = self.date_out(value) if value.is_a?(Datet) || value.is_a?(Time)
422
+
423
+ sql << "#{@sep_col}#{self.esc_col(key)}#{@sep_col} = "
424
+ sql << self.sqlval(value)
425
+ end
426
+
427
+ if arr_terms && arr_terms.length > 0
428
+ sql << " WHERE #{self.makeWhere(arr_terms)}"
429
+ end
430
+
431
+ return sql if args && args[:return_sql]
432
+
433
+ self.conn_exec do |driver|
434
+ driver.query(sql)
435
+ end
436
+ end
437
+
438
+ #Checks if a given terms exists. If it does, updates it to match data. If not inserts the row.
439
+ def upsert(table, data, terms, args = nil)
440
+ row = self.select(table, terms, "limit" => 1).fetch
441
+
442
+ if args && args[:buffer]
443
+ obj = args[:buffer]
444
+ else
445
+ obj = self
446
+ end
447
+
448
+ if row
449
+ obj.update(table, data, terms)
450
+ else
451
+ obj.insert(table, terms.merge(data))
452
+ end
453
+ end
454
+
455
+ SELECT_ARGS_ALLOWED_KEYS = [:limit, :limit_from, :limit_to]
456
+ #Makes a select from the given arguments: table-name, where-terms and other arguments as limits and orders. Also takes a block to avoid raping of memory.
457
+ def select(tablename, arr_terms = nil, args = nil, &block)
458
+ #Set up vars.
459
+ sql = ""
460
+ args_q = nil
461
+ select_sql = "*"
462
+
463
+ #Give 'cloned_ubuf' argument to 'q'-method.
464
+ if args && args[:cloned_ubuf]
465
+ args_q = {cloned_ubuf: true}
466
+ end
467
+
468
+ #Set up IDQuery-stuff if that is given in arguments.
469
+ if args && args[:idquery]
470
+ if args[:idquery] == true
471
+ select_sql = "`id`"
472
+ col = :id
473
+ else
474
+ select_sql = "`#{self.esc_col(args[:idquery])}`"
475
+ col = args[:idquery]
476
+ end
477
+ end
478
+
479
+ sql = "SELECT #{select_sql} FROM #{@sep_table}#{tablename}#{@sep_table}"
480
+
481
+ if arr_terms != nil && !arr_terms.empty?
482
+ sql << " WHERE #{self.makeWhere(arr_terms)}"
483
+ end
484
+
485
+ if args != nil
486
+ if args[:orderby]
487
+ sql << " ORDER BY #{args[:orderby]}"
488
+ end
489
+
490
+ if args[:limit]
491
+ sql << " LIMIT #{args[:limit]}"
492
+ end
493
+
494
+ if args[:limit_from] && args[:limit_to]
495
+ raise "'limit_from' was not numeric: '#{args[:limit_from]}'." if !(Float(args[:limit_from]) rescue false)
496
+ raise "'limit_to' was not numeric: '#{args[:limit_to]}'." if !(Float(args[:limit_to]) rescue false)
497
+ sql << " LIMIT #{args[:limit_from]}, #{args[:limit_to]}"
498
+ end
499
+ end
500
+
501
+ #Do IDQuery if given in arguments.
502
+ if args && args[:idquery]
503
+ res = Baza::Idquery.new(db: self, table: tablename, query: sql, col: col, &block)
504
+ else
505
+ res = q(sql, args_q, &block)
506
+ end
507
+
508
+ #Return result if a block wasnt given.
509
+ if block
510
+ return nil
511
+ else
512
+ return res
513
+ end
514
+ end
515
+
516
+ #Returns a single row from a database.
517
+ #
518
+ #===Examples
519
+ # row = db.single(:users, lastname: "Doe")
520
+ def single(tablename, arr_terms = nil, args = {})
521
+ args[:limit] = 1
522
+
523
+ #Experienced very weird memory leak if this was not done by block. Maybe bug in Ruby 1.9.2? - knj
524
+ self.select(tablename, arr_terms, args) do |data|
525
+ return data
526
+ end
527
+
528
+ return false
529
+ end
530
+
531
+ alias :selectsingle :single
532
+
533
+ #Deletes rows from the database.
534
+ #
535
+ #===Examples
536
+ # db.delete(:users, {lastname: "Doe"})
537
+ def delete(tablename, arr_terms, args = nil)
538
+ sql = "DELETE FROM #{@sep_table}#{tablename}#{@sep_table}"
539
+
540
+ if arr_terms != nil && !arr_terms.empty?
541
+ sql << " WHERE #{self.makeWhere(arr_terms)}"
542
+ end
543
+
544
+ return sql if args && args[:return_sql]
545
+
546
+ self.conn_exec do |driver|
547
+ driver.query(sql)
548
+ end
549
+
550
+ return nil
551
+ end
552
+
553
+ #Internally used to generate SQL.
554
+ #
555
+ #===Examples
556
+ # sql = db.makeWhere({lastname: "Doe"}, driver_obj)
557
+ def makeWhere(arr_terms, driver = nil)
558
+ sql = ""
559
+
560
+ first = true
561
+ arr_terms.each do |key, value|
562
+ if first
563
+ first = false
564
+ else
565
+ sql << " AND "
566
+ end
567
+
568
+ if value.is_a?(Array)
569
+ raise "Array for column '#{key}' was empty." if value.empty?
570
+ sql << "#{@sep_col}#{key}#{@sep_col} IN (#{Knj::ArrayExt.join(arr: value, sep: ",", surr: "'", callback: proc{|ele| self.esc(ele)})})"
571
+ elsif value.is_a?(Hash)
572
+ raise "Dont know how to handle hash."
573
+ else
574
+ sql << "#{@sep_col}#{key}#{@sep_col} = #{self.sqlval(value)}"
575
+ end
576
+ end
577
+
578
+ return sql
579
+ end
580
+
581
+ #Returns a driver-object based on the current thread and free driver-objects.
582
+ #
583
+ #===Examples
584
+ # db.conn_exec do |driver|
585
+ # str = driver.escape('something̈́')
586
+ # end
587
+ def conn_exec
588
+ raise "Call to closed database" if @closed
589
+
590
+ if tcur = Thread.current && Thread.current[:baza]
591
+ tid = self.__id__
592
+
593
+ if tcur[:baza].key?(tid)
594
+ yield(tcur[:baza][tid])
595
+ return nil
596
+ end
597
+ end
598
+
599
+ if @conns
600
+ conn = @conns.get_and_lock
601
+
602
+ begin
603
+ yield(conn)
604
+ return nil
605
+ ensure
606
+ @conns.free(conn)
607
+ end
608
+ elsif @conn
609
+ @mutex.synchronize do
610
+ yield(@conn)
611
+ return nil
612
+ end
613
+ end
614
+
615
+ raise "Could not figure out which driver to use?"
616
+ end
617
+
618
+ #Executes a query and returns the result.
619
+ #
620
+ #===Examples
621
+ # res = db.query('SELECT * FROM users')
622
+ # while data = res.fetch
623
+ # print data[:name]
624
+ # end
625
+ def query(string)
626
+ if @debug
627
+ print "SQL: #{string}\n"
628
+
629
+ if @debug.is_a?(Fixnum) && @debug >= 2
630
+ print caller.join("\n")
631
+ print "\n"
632
+ end
633
+ end
634
+
635
+ begin
636
+ self.conn_exec do |driver|
637
+ return driver.query(string)
638
+ end
639
+ rescue => e
640
+ e.message << " (SQL: #{string})" if @opts[:sql_to_error]
641
+ raise e
642
+ end
643
+ end
644
+
645
+ #Execute an ubuffered query and returns the result.
646
+ #
647
+ #===Examples
648
+ # db.query_ubuf('SELECT * FROM users') do |data|
649
+ # print data[:name]
650
+ # end
651
+ def query_ubuf(string, &block)
652
+ ret = nil
653
+
654
+ self.conn_exec do |driver|
655
+ ret = driver.query_ubuf(string, &block)
656
+ end
657
+
658
+ if block
659
+ ret.each(&block)
660
+ return nil
661
+ end
662
+
663
+ return ret
664
+ end
665
+
666
+ #Clones the connection, executes the given block and closes the connection again.
667
+ #
668
+ #===Examples
669
+ # db.cloned_conn do |conn|
670
+ # conn.q('SELCET * FROM users') do |data|
671
+ # print data[:name]
672
+ # end
673
+ # end
674
+ def cloned_conn(args = nil, &block)
675
+ clone_conn_args = {
676
+ threadsafe: false
677
+ }
678
+
679
+ clone_conn_args.merge!(args[:clone_args]) if args && args[:clone_args]
680
+ dbconn = self.clone_conn(clone_conn_args)
681
+
682
+ begin
683
+ yield(dbconn)
684
+ ensure
685
+ dbconn.close
686
+ end
687
+ end
688
+
689
+ #Executes a query and returns the result. If a block is given the result is iterated over that block instead and it returns nil.
690
+ #
691
+ #===Examples
692
+ # db.q('SELECT * FROM users') do |data|
693
+ # print data[:name]
694
+ # end
695
+ def q(str, args = nil, &block)
696
+ #If the query should be executed in a new connection unbuffered.
697
+ if args
698
+ if args[:cloned_ubuf]
699
+ raise "No block given." unless block
700
+
701
+ self.cloned_conn(clone_args: args[:clone_args]) do |cloned_conn|
702
+ ret = cloned_conn.query_ubuf(str)
703
+ ret.each(&block)
704
+ end
705
+
706
+ return nil
707
+ else
708
+ raise "Invalid arguments given: '#{args}'."
709
+ end
710
+ end
711
+
712
+ ret = self.query(str)
713
+
714
+ if block
715
+ ret.each(&block)
716
+ return nil
717
+ end
718
+
719
+ return ret
720
+ end
721
+
722
+ #Yields a query-buffer and flushes at the end of the block given.
723
+ def q_buffer(args = {}, &block)
724
+ Baza::QueryBuffer.new(args.merge(db: self), &block)
725
+ return nil
726
+ end
727
+
728
+ #Returns the last inserted ID.
729
+ #
730
+ #===Examples
731
+ # id = db.last_id
732
+ def last_id
733
+ self.conn_exec do |driver|
734
+ return driver.last_id
735
+ end
736
+ end
737
+
738
+ #Escapes a string to be safe-to-use in a query-string.
739
+ #
740
+ #===Examples
741
+ # db.q("INSERT INTO users (name) VALUES ('#{db.esc('John')}')")
742
+ def escape(string)
743
+ return @esc_driver.escape(string)
744
+ end
745
+
746
+ alias :esc :escape
747
+
748
+ #Escapes the given string to be used as a column.
749
+ def esc_col(str)
750
+ return @esc_driver.esc_col(str)
751
+ end
752
+
753
+ #Escapes the given string to be used as a table.
754
+ def esc_table(str)
755
+ return @esc_driver.esc_table(str)
756
+ end
757
+
758
+ #Returns a string which can be used in SQL with the current driver.
759
+ #===Examples
760
+ # str = db.date_out(Time.now) #=> "2012-05-20 22:06:09"
761
+ def date_out(date_obj = Datet.new, args = {})
762
+ if @esc_driver.respond_to?(:date_out)
763
+ return @esc_driver.date_out(date_obj, args)
764
+ end
765
+
766
+ return Datet.in(date_obj).dbstr(args)
767
+ end
768
+
769
+ #Takes a valid date-db-string and converts it into a Datet.
770
+ #===Examples
771
+ # db.date_in('2012-05-20 22:06:09') #=> 2012-05-20 22:06:09 +0200
772
+ def date_in(date_obj)
773
+ if @esc_driver.respond_to?(:date_in)
774
+ return @esc_driver.date_in(date_obj)
775
+ end
776
+
777
+ return Datet.in(date_obj)
778
+ end
779
+
780
+ #Returns the table-module and spawns it if it isnt already spawned.
781
+ def tables
782
+ unless @tables
783
+ @tables = Baza::Driver.const_get(@type_cc).const_get(:Tables).new(
784
+ db: self
785
+ )
786
+ end
787
+
788
+ return @tables
789
+ end
790
+
791
+ #Returns the columns-module and spawns it if it isnt already spawned.
792
+ def cols
793
+ unless @cols
794
+ @cols = Baza::Driver.const_get(@type_cc).const_get(:Columns).new(
795
+ db: self
796
+ )
797
+ end
798
+
799
+ return @cols
800
+ end
801
+
802
+ #Returns the index-module and spawns it if it isnt already spawned.
803
+ def indexes
804
+ unless @indexes
805
+ @indexes = Baza::Driver.const_get(@type_cc).const_get(:Indexes).new(
806
+ db: self
807
+ )
808
+ end
809
+
810
+ return @indexes
811
+ end
812
+
813
+ #Returns the SQLSpec-module and spawns it if it isnt already spawned.
814
+ def sqlspecs
815
+ unless @sqlspecs
816
+ @sqlspecs = Baza::Driver.const_get(@type_cc).const_get(:Sqlspecs).new(
817
+ db: self
818
+ )
819
+ end
820
+
821
+ return @sqlspecs
822
+ end
823
+
824
+ #Beings a transaction and commits when the block ends.
825
+ #
826
+ #===Examples
827
+ # db.transaction do |db|
828
+ # db.insert(:users, name: "John")
829
+ # db.insert(:users, name: "Kasper")
830
+ # end
831
+ def transaction(&block)
832
+ conn_exec do |driver|
833
+ driver.transaction(&block)
834
+ end
835
+
836
+ return nil
837
+ end
838
+
839
+ #Optimizes all tables in the database.
840
+ def optimize(args = nil)
841
+ STDOUT.puts "Beginning optimization of database." if @debug || (args && args[:debug])
842
+ tables.list do |table|
843
+ STDOUT.puts "Optimizing table: '#{table.name}'." if @debug || (args && args[:debug])
844
+ table.optimize
845
+ end
846
+
847
+ return nil
848
+ end
849
+
850
+ #Proxies the method to the driver.
851
+ #
852
+ #===Examples
853
+ # db.method_on_driver
854
+ def method_missing(method_name, *args)
855
+ self.conn_exec do |driver|
856
+ if driver.respond_to?(method_name.to_sym)
857
+ return driver.send(method_name, *args)
858
+ end
859
+ end
860
+
861
+ raise "Method not found: '#{method_name}'."
862
+ end
863
+
864
+ def to_s
865
+ "#<Baza::Db driver \"#{@opts[:type]}\">"
866
+ end
867
+
868
+ def inspect
869
+ to_s
870
+ end
871
+ end