lib/baza/db.rb in baza-0.0.20 vs lib/baza/db.rb in baza-0.0.21

- old
+ new

@@ -10,15 +10,21 @@ # # db.q("SELECT * FROM users") do |data| # print data[:name] # end class Baza::Db - attr_reader :sep_col, :sep_table, :sep_val, :opts, :driver, :int_types + include SimpleDelegate + delegate :last_id, :upsert, :upsert_duplicate_key, to: :commands + delegate :current_database, :current_database_name, :with_database, to: :databases + delegate :close, :count, :delete, :esc, :escape, :escape_column, :escape_table, :escape_database, :escape_index, :insert, :select, :single, :sqlval, :sql_make_where, to: :driver + + attr_reader :sep_database, :sep_col, :sep_table, :sep_val, :sep_index, :opts, :driver, :int_types + # Returns an array containing hashes of information about each registered driver. def self.drivers - path = "#{File.dirname(__FILE__)}/drivers" + path = "#{File.dirname(__FILE__)}/driver" drivers = [] Dir.foreach(path) do |file| next if file.to_s.slice(0, 1) == "." fp = "#{path}/#{file}" @@ -66,12 +72,14 @@ self.opts = opts unless opts.nil? @int_types = [:int, :bigint, :tinyint, :smallint, :mediumint] @debug = @opts[:debug] @driver = spawn + @sep_database = @driver.sep_database @sep_table = @driver.sep_table @sep_col = @driver.sep_col + @sep_index = @driver.sep_index @sep_val = @driver.sep_val return unless block_given? begin @@ -102,17 +110,17 @@ # Spawns a new driver (useally done automatically). #===Examples # driver_instance = db.spawn def spawn raise "No type given (#{@opts.keys.join(",")})." unless @opts[:type] - rpath = "#{File.dirname(__FILE__)}/drivers/#{@opts[:type]}.rb" + rpath = "#{File.dirname(__FILE__)}/driver/#{@opts.fetch(:type)}.rb" require rpath if File.exist?(rpath) Baza::Driver.const_get(@type_cc).new(self) end # Registers a driver to the current thread. - def get_and_register_thread + def 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] @@ -139,19 +147,17 @@ @driver.close if @driver @driver = nil @closed = true end - # rubocop:disable Style/TrivialAccessors def closed? - # rubocop:enable Style/TrivialAccessors @closed end # Clones the current database-connection with possible extra arguments. def clone_conn(args = {}) - conn = Baza::Db.new(opts = @opts.clone.merge(args)) + conn = Baza::Db.new(@opts.merge(args)) if block_given? begin yield(conn) ensure @@ -162,11 +168,11 @@ else return conn end end - COPY_TO_ALLOWED_ARGS = [:tables, :debug] + COPY_TO_ALLOWED_ARGS = [:tables, :debug].freeze # Copies the content of the current database to another instance of Baza::Db. def copy_to(db, args = {}) debug = args[:debug] raise "No tables given." unless data[:tables] @@ -219,43 +225,22 @@ end {tables: tables_ret} end - def insert(table_name, data, args = nil) - @driver.insert(table_name, data, args) - end - def add_sql_to_error(error, sql) error.message << " (SQL: #{sql})" end - # 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. - 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 - # 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"} # ]) - def insert_multi(tablename, arr_hashes, args = nil) + def insert_multi(tablename, arr_hashes, args = {}) 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) @@ -282,197 +267,30 @@ # Simple updates rows. # #===Examples # db.update(:users, {name: "John"}, {lastname: "Doe"}) - 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? + def update(table_name, data, terms = {}, args = {}) + command = Baza::SqlQueries::GenericUpdate.new( + db: self, + table_name: table_name, + data: data, + terms: terms, + buffer: args[:buffer] + ) - 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 - - # Checks if a given terms exists. If it does, updates it to match data. If not inserts the row. - def upsert(table, data, terms, args = nil) - row = single(table, terms) - - if args && args[:buffer] - obj = args[:buffer] + if args[:return_sql] + command.to_sql else - obj = self + command.execute end - - if row - obj.update(table, data, terms) - else - obj.insert(table, terms.merge(data)) - end end - SELECT_ARGS_ALLOWED_KEYS = [:limit, :limit_from, :limit_to] - # 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. - 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 + def in_transaction? + @in_transaction end - 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 - - # Returns a single row from a database. - # - #===Examples - # row = db.single(:users, lastname: "Doe") - 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 - - alias_method :selectsingle, :single - - # Deletes rows from the database. - # - #===Examples - # db.delete(:users, {lastname: "Doe"}) - 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 - - # Internally used to generate SQL. - # - #===Examples - # sql = db.makeWhere({lastname: "Doe"}, driver_obj) - 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 - # Executes a query and returns the result. # #===Examples # res = db.query('SELECT * FROM users') # while data = res.fetch @@ -517,11 +335,11 @@ # #===Examples # db.query_ubuf('SELECT * FROM users') do |data| # print data[:name] # end - def query_ubuf(string, args = nil, &block) + def query_ubuf(string, _args = nil, &block) ret = @driver.query_ubuf(string) if block ret.each(&block) return nil @@ -553,42 +371,10 @@ def q_buffer(args = {}, &block) Baza::QueryBuffer.new(args.merge(db: self), &block) nil end - # Returns the last inserted ID. - # - #===Examples - # id = db.last_id - def last_id - @driver.last_id - end - - # Escapes a string to be safe-to-use in a query-string. - # - #===Examples - # db.q("INSERT INTO users (name) VALUES ('#{db.esc('John')}')") - def escape(string) - @driver.escape(string) - end - - alias_method :esc, :escape - - # Escapes the given string to be used as a column. - def escape_column(str) - @driver.escape_column(str) - end - - # Escapes the given string to be used as a table. - def escape_table(str) - @driver.escape_table(str) - end - - def escape_database(str) - @driver.escape_database(str) - end - # 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" def date_out(date_obj = Datet.new, args = {}) return @driver.date_out(date_obj, args) if @driver.respond_to?(:date_out) @@ -598,39 +384,29 @@ # 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 def date_in(date_obj) return @driver.date_in(date_obj) if @driver.respond_to?(:date_in) - Datet.in(date_obj) end - def databases - require_relative "drivers/#{@opts.fetch(:type)}/databases" - @databases ||= Baza::Driver.const_get(@type_cc).const_get(:Databases).new(db: self) - end + # Defines all the driver methods: tables, columns and so on + DRIVER_PARTS = [:databases, :tables, :commands, :columns, :indexes, :users, :sqlspecs].freeze + DRIVER_PARTS.each do |driver_part| + define_method(driver_part) do + if instance_variable_defined?(:"@#{driver_part}") + instance_variable_get(:"@#{driver_part}") + else + require_relative "driver/#{@opts.fetch(:type)}/#{driver_part}" - # Returns the table-module and spawns it if it isnt already spawned. - def tables - @tables ||= Baza::Driver.const_get(@type_cc).const_get(:Tables).new(db: self) + instance = Baza::Driver.const_get(@type_cc).const_get(StringCases.snake_to_camel(driver_part)).new(db: self) + instance_variable_set(:"@#{driver_part}", instance) + instance + end + end end - # Returns the columns-module and spawns it if it isnt already spawned. - def cols - @cols || Baza::Driver.const_get(@type_cc).const_get(:Columns).new(db: self) - end - - # Returns the index-module and spawns it if it isnt already spawned. - def indexes - @indexes ||= Baza::Driver.const_get(@type_cc).const_get(:Indexes).new(db: self) - end - - # Returns the SQLSpec-module and spawns it if it isnt already spawned. - def sqlspecs - @sqlspecs ||= Baza::Driver.const_get(@type_cc).const_get(:Sqlspecs).new(db: self) - end - def supports_multiple_databases? if @driver.respond_to?(:supports_multiple_databases?) @driver.supports_multiple_databases? else false @@ -651,28 +427,50 @@ # db.transaction do |db| # db.insert(:users, name: "John") # db.insert(:users, name: "Kasper") # end def transaction(&block) - @driver.transaction(&block) - nil + @in_transaction = true + begin + @driver.transaction(&block) + ensure + @in_transaction = false + end + + self end # Optimizes all tables in the database. 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 + self end def to_s - "#<Baza::Db driver=\"#{@opts[:type]}\">" + "#<Baza::Db driver=\"#{@opts.fetch(:type)}\">" end def inspect to_s + end + + def new_query + Baza::SqlQueries::Select.new(db: self) + end + + def sqlite? + @sqlite ||= @driver.class.name.downcase.include?("sqlite") + end + + def mysql? + @mysql ||= @driver.class.name.downcase.include?("mysql") + end + + def postgres? + @postgres ||= @driver.class.name.downcase.include?("pg") end end