Class: Baza::Db

Inherits:
Object
  • Object
show all
Defined in:
lib/baza/db.rb

Overview

A wrapper of several possible database-types.

Examples

db = Baza::Db.new(type: :mysql2, db: “mysql”, user: “user”, pass: “password”) mysql_table = db.tables name = mysql_table.name cols = mysql_table.columns

db = Baza::Db.new(type: :sqlite3, path: “some_db.sqlite3”)

db.q(“SELECT * FROM users”) do |data|

print data[:name]

end

Constant Summary collapse

COPY_TO_ALLOWED_ARGS =
[:tables, :debug]
SELECT_ARGS_ALLOWED_KEYS =
[:limit, :limit_from, :limit_to]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts) ⇒ Db

Returns a new instance of Db.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/baza/db.rb', line 63

def initialize(opts)
  @driver = opts.delete(:driver) if opts[:driver]
  Baza.load_driver(opts.fetch(:type))
  self.opts = opts unless opts.nil?
  @int_types = [:int, :bigint, :tinyint, :smallint, :mediumint]

  @debug = @opts[:debug]
  @driver = spawn
  @sep_table = @driver.sep_table
  @sep_col = @driver.sep_col
  @sep_val = @driver.sep_val

  return unless block_given?

  begin
    yield self
  ensure
    close
  end
end

Instance Attribute Details

#driverObject (readonly)

Returns the value of attribute driver.



15
16
17
# File 'lib/baza/db.rb', line 15

def driver
  @driver
end

#int_typesObject (readonly)

Returns the value of attribute int_types.



15
16
17
# File 'lib/baza/db.rb', line 15

def int_types
  @int_types
end

#optsObject

Returns the value of attribute opts.



15
16
17
# File 'lib/baza/db.rb', line 15

def opts
  @opts
end

#sep_colObject (readonly)

Returns the value of attribute sep_col.



15
16
17
# File 'lib/baza/db.rb', line 15

def sep_col
  @sep_col
end

#sep_tableObject (readonly)

Returns the value of attribute sep_table.



15
16
17
# File 'lib/baza/db.rb', line 15

def sep_table
  @sep_table
end

#sep_valObject (readonly)

Returns the value of attribute sep_val.



15
16
17
# File 'lib/baza/db.rb', line 15

def sep_val
  @sep_val
end

Class Method Details

.driversObject

Returns an array containing hashes of information about each registered driver.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/baza/db.rb', line 18

def self.drivers
  path = "#{File.dirname(__FILE__)}/drivers"
  drivers = []

  Dir.foreach(path) do |file|
    next if file.to_s.slice(0, 1) == "."
    fp = "#{path}/#{file}"
    next unless File.directory?(fp)

    driver_file = "#{fp}/#{file}.rb"
    class_name = StringCases.snake_to_camel(file).to_sym

    drivers << {
      name: file,
      driver_path: driver_file,
      class_name: class_name
    }
  end

  drivers
end

.from_object(args) ⇒ Object

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



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/baza/db.rb', line 41

def self.from_object(args)
  args = {object: args} unless args.is_a?(Hash)
  raise "No :object was given." unless args[:object]

  Baza::Db.drivers.each do |driver|
    const = Baza::Driver.const_get(driver[:class_name])
    next unless const.respond_to?(:from_object)

    obj = const.from_object(args)
    next unless obj.is_a?(Hash) && obj[:type] == :success
    if obj[:args]
      new_args = obj[:args]
      new_args = new_args.merge(args[:new_args]) if args[:new_args]
      return Baza::Db.new(new_args)
    else
      raise "Didnt know what to do."
    end
  end

  raise "Could not figure out what to do what object of type: '#{args[:object].class.name}'."
end

Instance Method Details

#add_sql_to_error(error, sql) ⇒ Object



228
229
230
# File 'lib/baza/db.rb', line 228

def add_sql_to_error(error, sql)
  error.message << " (SQL: #{sql})"
end

#argsObject



84
85
86
# File 'lib/baza/db.rb', line 84

def args
  @opts
end

#clone_conn(args = {}) ⇒ Object

Clones the current database-connection with possible extra arguments.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/baza/db.rb', line 151

def clone_conn(args = {})
  conn = Baza::Db.new(opts = @opts.clone.merge(args))

  if block_given?
    begin
      yield(conn)
    ensure
      conn.close
    end

    return nil
  else
    return conn
  end
end

#cloned_conn(args = nil, &_block) ⇒ Object

Clones the connection, executes the given block and closes the connection again.

Examples

db.cloned_conn do |conn|

conn.q('SELCET * FROM users') do |data|
  print data[:name]
end

end



541
542
543
544
545
546
547
548
549
550
# File 'lib/baza/db.rb', line 541

def cloned_conn(args = nil, &_block)
  clone_conn_args = args[:clone_args] || {}
  dbconn = clone_conn(clone_conn_args)

  begin
    yield(dbconn)
  ensure
    dbconn.close
  end
end

#closeObject

The all driver-database-connections.



138
139
140
141
142
# File 'lib/baza/db.rb', line 138

def close
  @driver.close if @driver
  @driver = nil
  @closed = true
end

#closed?Boolean

rubocop:disable Style/TrivialAccessors

Returns:

  • (Boolean)


145
146
147
148
# File 'lib/baza/db.rb', line 145

def closed?
  # rubocop:enable Style/TrivialAccessors
  @closed
end

#colsObject

Returns the columns-module and spawns it if it isnt already spawned.



618
619
620
# File 'lib/baza/db.rb', line 618

def cols
  @cols || Baza::Driver.const_get(@type_cc).const_get(:Columns).new(db: self)
end

#copy_to(db, args = {}) ⇒ Object

Copies the content of the current database to another instance of Baza::Db.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/baza/db.rb', line 169

def copy_to(db, args = {})
  debug = args[:debug]
  raise "No tables given." unless data[:tables]

  data[:tables].each do |table|
    table_args = nil
    table_args = args[:tables][table[:name]] if args && args[:tables] && args[:tables][table[:name].to_sym]
    next if table_args && table_args[:skip]
    table.delete(:indexes) if table.key?(:indexes) && args[:skip_indexes]

    table_name = table.delete(:name)
    puts "Creating table: '#{table_name}'." if debug
    db.tables.create(table_name, table)

    limit_from = 0
    limit_incr = 1000

    loop do
      puts "Copying rows (#{limit_from}, #{limit_incr})." if debug
      ins_arr = []
      select(table_name, {}, limit_from: limit_from, limit_to: limit_incr) do |d_rows|
        col_args = nil

        if table_args && table_args[:columns]
          d_rows.each do |col_name, _col_data|
            col_args = table_args[:columns][col_name] if table_args && table_args[:columns]
            d_rows[col_name] = "" if col_args && col_args[:empty]
          end
        end

        ins_arr << d_rows
      end

      break if ins_arr.empty?

      puts "Insertering #{ins_arr.length} rows." if debug
      db.insert_multi(table_name, ins_arr)
      limit_from += limit_incr
    end
  end
end

#count(tablename, arr_terms = nil) ⇒ Object



403
404
405
406
407
408
409
410
411
412
413
414
415
# File 'lib/baza/db.rb', line 403

def count(tablename, arr_terms = nil)
  # Set up vars.
  sql = ""
  args_q = nil

  sql = "SELECT COUNT(*) AS count FROM #{@sep_table}#{tablename}#{@sep_table}"

  if !arr_terms.nil? && !arr_terms.empty?
    sql << " WHERE #{makeWhere(arr_terms)}"
  end

  q(sql).fetch.fetch(:count).to_i
end

#dataObject

Returns the data of this database in a hash.

Examples

data = db.data tables_hash = data



215
216
217
218
219
220
221
222
# File 'lib/baza/db.rb', line 215

def data
  tables_ret = []
  tables.list do |table|
    tables_ret << table.data
  end

  {tables: tables_ret}
end

#databasesObject



607
608
609
610
# File 'lib/baza/db.rb', line 607

def databases
  require_relative "drivers/#{@opts.fetch(:type)}/databases"
  @databases ||= Baza::Driver.const_get(@type_cc).const_get(:Databases).new(db: self)
end

#date_in(date_obj) ⇒ Object

Takes a valid date-db-string and converts it into a Datet.

Examples

db.date_in(‘2012-05-20 22:06:09’) #=> 2012-05-20 22:06:09 +0200



601
602
603
604
605
# File 'lib/baza/db.rb', line 601

def date_in(date_obj)
  return @driver.date_in(date_obj) if @driver.respond_to?(:date_in)

  Datet.in(date_obj)
end

#date_out(date_obj = Datet.new, args = {}) ⇒ Object

Returns a string which can be used in SQL with the current driver.

Examples

str = db.date_out(Time.now) #=> “2012-05-20 22:06:09”



593
594
595
596
# File 'lib/baza/db.rb', line 593

def date_out(date_obj = Datet.new, args = {})
  return @driver.date_out(date_obj, args) if @driver.respond_to?(:date_out)
  Datet.in(date_obj).dbstr(args)
end

#delete(tablename, arr_terms, args = nil) ⇒ Object

Deletes rows from the database.

Examples

db.delete(:users, “Doe”)



432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/baza/db.rb', line 432

def delete(tablename, arr_terms, args = nil)
  sql = "DELETE FROM #{@sep_table}#{tablename}#{@sep_table}"

  if !arr_terms.nil? && !arr_terms.empty?
    sql << " WHERE #{makeWhere(arr_terms)}"
  end

  return sql if args && args[:return_sql]

  query(sql)
  nil
end

#escape(string) ⇒ Object Also known as: esc

Escapes a string to be safe-to-use in a query-string.

Examples

db.q(“INSERT INTO users (name) VALUES (‘#Baza::Db.dbdb.esc(’John’)‘)”)



570
571
572
# File 'lib/baza/db.rb', line 570

def escape(string)
  @driver.escape(string)
end

#escape_column(str) ⇒ Object

Escapes the given string to be used as a column.



577
578
579
# File 'lib/baza/db.rb', line 577

def escape_column(str)
  @driver.escape_column(str)
end

#escape_database(str) ⇒ Object



586
587
588
# File 'lib/baza/db.rb', line 586

def escape_database(str)
  @driver.escape_database(str)
end

#escape_table(str) ⇒ Object

Escapes the given string to be used as a table.



582
583
584
# File 'lib/baza/db.rb', line 582

def escape_table(str)
  @driver.escape_table(str)
end

#get_and_register_threadObject

Registers a driver to the current thread.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/baza/db.rb', line 113

def get_and_register_thread
  raise "Baza-object is not in threadding mode" unless @conns

  thread_cur = Thread.current
  tid = __id__
  thread_cur[:baza] = {} unless thread_cur[:baza]

  if thread_cur[:baza][tid]
    # An object has already been spawned - free that first to avoid endless "used" objects.
    free_thread
  end

  thread_cur[:baza][tid] = @conns.get_and_lock unless thread_cur[:baza][tid]

  # If block given then be ensure to free thread after yielding.
  return unless block_given?

  begin
    yield
  ensure
    free_thread
  end
end

#indexesObject

Returns the index-module and spawns it if it isnt already spawned.



623
624
625
# File 'lib/baza/db.rb', line 623

def indexes
  @indexes ||= Baza::Driver.const_get(@type_cc).const_get(:Indexes).new(db: self)
end

#insert(table_name, data, args = nil) ⇒ Object



224
225
226
# File 'lib/baza/db.rb', line 224

def insert(table_name, data, args = nil)
  @driver.insert(table_name, data, args)
end

#insert_multi(tablename, arr_hashes, args = nil) ⇒ Object

Simply and optimal insert multiple rows into a table in a single query. Uses the drivers functionality if supported or inserts each row manually.

Examples

db.insert_multi(:users, [

{name: "John", lastname: "Doe"},
{name: "Kasper", lastname: "Johansen"}

])



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/baza/db.rb', line 256

def insert_multi(tablename, arr_hashes, args = nil)
  return false if arr_hashes.empty?

  if @driver.respond_to?(:insert_multi)
    if args && args[:return_sql]
      res = @driver.insert_multi(tablename, arr_hashes, args)
      if res.is_a?(String)
        return [res]
      elsif res.is_a?(Array)
        return res
      else
        raise "Unknown result: '#{res.class.name}'."
      end
    end

    @driver.insert_multi(tablename, arr_hashes, args)
  else
    transaction do
      arr_hashes.each do |hash|
        insert(tablename, hash, args)
      end
    end

    return nil
  end
end

#inspectObject



675
676
677
# File 'lib/baza/db.rb', line 675

def inspect
  to_s
end

#last_idObject

Returns the last inserted ID.

Examples

id = db.last_id



562
563
564
# File 'lib/baza/db.rb', line 562

def last_id
  @driver.last_id
end

#makeWhere(arr_terms, _driver = nil) ⇒ Object

Internally used to generate SQL.

Examples

sql = db.makeWhere(“Doe”, driver_obj)



449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/baza/db.rb', line 449

def makeWhere(arr_terms, _driver = nil)
  sql = ""

  first = true
  arr_terms.each do |key, value|
    if first
      first = false
    else
      sql << " AND "
    end

    if value.is_a?(Array)
      raise "Array for column '#{key}' was empty." if value.empty?
      values = value.map { |v| "'#{escape(v)}'" }.join(",")
      sql << "#{@sep_col}#{key}#{@sep_col} IN (#{values})"
    elsif value.is_a?(Hash)
      raise "Dont know how to handle hash."
    else
      sql << "#{@sep_col}#{key}#{@sep_col} = #{sqlval(value)}"
    end
  end

  sql
end

#optimize(args = nil) ⇒ Object

Optimizes all tables in the database.



661
662
663
664
665
666
667
668
669
# File 'lib/baza/db.rb', line 661

def optimize(args = nil)
  STDOUT.puts "Beginning optimization of database." if @debug || (args && args[:debug])
  tables.list do |table|
    STDOUT.puts "Optimizing table: '#{table.name}'." if @debug || (args && args[:debug])
    table.optimize
  end

  nil
end

#q_buffer(args = {}, &block) ⇒ Object

Yields a query-buffer and flushes at the end of the block given.



553
554
555
556
# File 'lib/baza/db.rb', line 553

def q_buffer(args = {}, &block)
  Baza::QueryBuffer.new(args.merge(db: self), &block)
  nil
end

#query(string, args = nil, &block) ⇒ Object Also known as: q

Executes a query and returns the result.

Examples

res = db.query(‘SELECT * FROM users’) while data = res.fetch

print data[:name]

end



481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
# File 'lib/baza/db.rb', line 481

def query(string, args = nil, &block)
  if @debug
    print "SQL: #{string}\n"

    if @debug.is_a?(Fixnum) && @debug >= 2
      print caller.join("\n")
      print "\n"
    end
  end

  # If the query should be executed in a new connection unbuffered.
  if args && args[:cloned_ubuf]
    raise "No block given." unless block

    cloned_conn(clone_args: args[:clone_args]) do |cloned_conn|
      return cloned_conn.query_ubuf(string, args, &block)
    end

    return nil
  end

  return query_ubuf(string, args, &block) if args && args[:type] == :unbuffered

  ret = @driver.query(string)

  if block && ret
    ret.each(&block)
    return nil
  end

  ret
end

#query_ubuf(string, args = nil, &block) ⇒ Object

Execute an ubuffered query and returns the result.

Examples

db.query_ubuf(‘SELECT * FROM users’) do |data|

print data[:name]

end



522
523
524
525
526
527
528
529
530
531
# File 'lib/baza/db.rb', line 522

def query_ubuf(string, args = nil, &block)
  ret = @driver.query_ubuf(string)

  if block
    ret.each(&block)
    return nil
  end

  ret
end

#select(tablename, arr_terms = nil, args = nil, &block) ⇒ Object

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.



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/baza/db.rb', line 335

def select(tablename, arr_terms = nil, args = nil, &block)
  # Set up vars.
  sql = ""
  args_q = nil
  select_sql = "*"

  # Give 'cloned_ubuf' argument to 'q'-method.
  args_q = {cloned_ubuf: true} if args && args[:cloned_ubuf]

  # Set up IDQuery-stuff if that is given in arguments.
  if args && args[:idquery]
    if args[:idquery] == true
      select_sql = "`id`"
      col = :id
    else
      select_sql = "`#{escape_column(args[:idquery])}`"
      col = args[:idquery]
    end
  end

  sql = "SELECT #{select_sql} FROM"

  if tablename.is_a?(Array)
    sql << " #{@sep_table}#{tablename.first}#{@sep_table}.#{@sep_table}#{tablename.last}#{@sep_table}"
  else
    sql << " #{@sep_table}#{tablename}#{@sep_table}"
  end

  if !arr_terms.nil? && !arr_terms.empty?
    sql << " WHERE #{makeWhere(arr_terms)}"
  end

  unless args.nil?
    sql << " ORDER BY #{args[:orderby]}" if args[:orderby]
    sql << " LIMIT #{args[:limit]}" if args[:limit]

    if args[:limit_from] && args[:limit_to]
      begin
        Float(args[:limit_from])
      rescue
        raise "'limit_from' was not numeric: '#{args[:limit_from]}'."
      end

      begin
        Float(args[:limit_to])
      rescue
        raise "'limit_to' was not numeric: '#{args[:limit_to]}'."
      end

      sql << " LIMIT #{args[:limit_from]}, #{args[:limit_to]}"
    end
  end

  # Do IDQuery if given in arguments.
  if args && args[:idquery]
    res = Baza::Idquery.new(db: self, table: tablename, query: sql, col: col, &block)
  else
    res = q(sql, args_q, &block)
  end

  # Return result if a block wasnt given.
  if block
    return nil
  else
    return res
  end
end

#single(tablename, terms = nil, args = {}) ⇒ Object Also known as: selectsingle

Returns a single row from a database.

Examples

row = db.single(:users, lastname: “Doe”)



421
422
423
424
# File 'lib/baza/db.rb', line 421

def single(tablename, terms = nil, args = {})
  # Experienced very weird memory leak if this was not done by block. Maybe bug in Ruby 1.9.2? - knj
  select(tablename, terms, args.merge(limit: 1)).fetch
end

#spawnObject

Spawns a new driver (useally done automatically).

Examples

driver_instance = db.spawn



105
106
107
108
109
110
# File 'lib/baza/db.rb', line 105

def spawn
  raise "No type given (#{@opts.keys.join(",")})." unless @opts[:type]
  rpath = "#{File.dirname(__FILE__)}/drivers/#{@opts[:type]}.rb"
  require rpath if File.exist?(rpath)
  Baza::Driver.const_get(@type_cc).new(self)
end

#sqlspecsObject

Returns the SQLSpec-module and spawns it if it isnt already spawned.



628
629
630
# File 'lib/baza/db.rb', line 628

def sqlspecs
  @sqlspecs ||= Baza::Driver.const_get(@type_cc).const_get(:Sqlspecs).new(db: self)
end

#sqlval(val) ⇒ Object

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.



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/baza/db.rb', line 233

def sqlval(val)
  return @conn.sqlval(val) if @conn.respond_to?(:sqlval)

  if val.is_a?(Fixnum) || val.is_a?(Integer)
    return val.to_s
  elsif val == nil
    return "NULL"
  elsif val.is_a?(Date)
    return "#{@sep_val}#{Datet.in(val).dbstr(time: false)}#{@sep_val}"
  elsif val.is_a?(Time) || val.is_a?(DateTime)
    return "#{@sep_val}#{Datet.in(val).dbstr}#{@sep_val}"
  else
    return "#{@sep_val}#{escape(val)}#{@sep_val}"
  end
end

#supports_multiple_databases?Boolean

Returns:

  • (Boolean)


632
633
634
635
636
637
638
# File 'lib/baza/db.rb', line 632

def supports_multiple_databases?
  if @driver.respond_to?(:supports_multiple_databases?)
    @driver.supports_multiple_databases?
  else
    false
  end
end

#supports_type_translation?Boolean

Returns:

  • (Boolean)


640
641
642
643
644
645
646
# File 'lib/baza/db.rb', line 640

def supports_type_translation?
  if @driver.respond_to?(:supports_type_translation?)
    @driver.supports_multiple_databases?
  else
    false
  end
end

#tablesObject

Returns the table-module and spawns it if it isnt already spawned.



613
614
615
# File 'lib/baza/db.rb', line 613

def tables
  @tables ||= Baza::Driver.const_get(@type_cc).const_get(:Tables).new(db: self)
end

#to_sObject



671
672
673
# File 'lib/baza/db.rb', line 671

def to_s
  "#<Baza::Db driver=\"#{@opts[:type]}\">"
end

#transaction(&block) ⇒ Object

Beings a transaction and commits when the block ends.

Examples

db.transaction do |db|

db.insert(:users, name: "John")
db.insert(:users, name: "Kasper")

end



655
656
657
658
# File 'lib/baza/db.rb', line 655

def transaction(&block)
  @driver.transaction(&block)
  nil
end

#update(tablename, hash_update, arr_terms = {}, args = nil) ⇒ Object

Simple updates rows.

Examples

db.update(:users, “John”, “Doe”)



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/baza/db.rb', line 287

def update(tablename, hash_update, arr_terms = {}, args = nil)
  raise "'hash_update' was not a hash: '#{hash_update.class.name}'." unless hash_update.is_a?(Hash)
  return false if hash_update.empty?

  sql = ""
  sql << "UPDATE #{@sep_col}#{tablename}#{@sep_col} SET "

  first = true
  hash_update.each do |key, value|
    if first
      first = false
    else
      sql << ", "
    end

    # Convert dates to valid dbstr.
    value = date_out(value) if value.is_a?(Datet) || value.is_a?(Time)

    sql << "#{@sep_col}#{escape_column(key)}#{@sep_col} = "
    sql << sqlval(value)
  end

  sql << " WHERE #{makeWhere(arr_terms)}" if arr_terms && arr_terms.length > 0

  return sql if args && args[:return_sql]

  query(sql)
end

#upsert(table, data, terms, args = nil) ⇒ Object

Checks if a given terms exists. If it does, updates it to match data. If not inserts the row.



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/baza/db.rb', line 317

def upsert(table, data, terms, args = nil)
  row = single(table, terms)

  if args && args[:buffer]
    obj = args[:buffer]
  else
    obj = self
  end

  if row
    obj.update(table, data, terms)
  else
    obj.insert(table, terms.merge(data))
  end
end