baza 0.0.14 → 0.0.15

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