lib/og/store/mysql.rb in og-0.20.0 vs lib/og/store/mysql.rb in og-0.21.0

- old
+ new

@@ -1,313 +1,317 @@ begin - require 'mysql' + require 'mysql' rescue Object => ex - Logger.error 'Ruby-Mysql bindings are not installed!' - Logger.error 'Trying to uses the pure-Ruby binding included in Og' - begin - # Attempt to use the included pure ruby version. - require 'vendor/mysql' - require 'vendor/mysql411' - rescue Object => ex - Logger.error ex - end + Logger.error 'Ruby-Mysql bindings are not installed!' + Logger.error 'Trying to uses the pure-Ruby binding included in Og' + begin + # Attempt to use the included pure ruby version. + require 'vendor/mysql' + require 'vendor/mysql411' + rescue Object => ex + Logger.error ex + end end require 'og/store/sql' # Customize the standard mysql resultset to make # more compatible with Og. class Mysql::Result - def blank? - 0 == num_rows - end + def blank? + 0 == num_rows + end - alias_method :next, :fetch_row + alias_method :next, :fetch_row - def each_row - each do |row| - yield(row, 0) - end - end - - def first_value - val = fetch_row[0] - free - return val - end - - alias_method :close, :free + def each_row + each do |row| + yield(row, 0) + end + end + + def first_value + val = fetch_row[0] + free + return val + end + + alias_method :close, :free + + def fields + fetch_fields.map { |f| f.name } + end end module Og module MysqlUtils - include SqlUtils - - def escape(str) - return nil unless str - return Mysql.quote(str) - end - - def quote(val) - case val - when Fixnum, Integer, Float - val ? val.to_s : 'NULL' - when String - val ? "'#{escape(val)}'" : 'NULL' - when Time - val ? "'#{timestamp(val)}'" : 'NULL' - when Date - val ? "'#{date(val)}'" : 'NULL' - when TrueClass - val ? "'1'" : 'NULL' - else - # gmosx: keep the '' for nil symbols. - val ? escape(val.to_yaml) : '' - end - end + include SqlUtils + + def escape(str) + return nil unless str + return Mysql.quote(str) + end + + def quote(val) + case val + when Fixnum, Integer, Float + val ? val.to_s : 'NULL' + when String + val ? "'#{escape(val)}'" : 'NULL' + when Time + val ? "'#{timestamp(val)}'" : 'NULL' + when Date + val ? "'#{date(val)}'" : 'NULL' + when TrueClass + val ? "'1'" : 'NULL' + else + # gmosx: keep the '' for nil symbols. + val ? escape(val.to_yaml) : '' + end + end end # A Store that persists objects into a MySQL database. # To read documentation about the methods, consult the documentation # for SqlStore and Store. class MysqlStore < SqlStore - extend MysqlUtils - include MysqlUtils + extend MysqlUtils + include MysqlUtils - def self.create(options) - # gmosx: system is used to avoid shell expansion. - system 'mysqladmin', '-f', "--user=#{options[:user]}", - "--password=#{options[:password]}", - 'create', options[:name] - super - end + def self.create(options) + # gmosx: system is used to avoid shell expansion. + system 'mysqladmin', '-f', "--user=#{options[:user]}", + "--password=#{options[:password]}", + 'create', options[:name] + super + end - def self.destroy(options) - system 'mysqladmin', '-f', "--user=#{options[:user]}", - "--password=#{options[:password]}", 'drop', - options[:name] - super - end + def self.destroy(options) + system 'mysqladmin', '-f', "--user=#{options[:user]}", + "--password=#{options[:password]}", 'drop', + options[:name] + super + end - def initialize(options) - super + def initialize(options) + super - @typemap.update(TrueClass => 'tinyint') + @typemap.update(TrueClass => 'tinyint') - @conn = Mysql.connect( - options[:address] || 'localhost', - options[:user], - options[:password], - options[:name] - ) - - # You should set recconect to true to avoid MySQL has - # gone away errors. - - if @conn.respond_to? :reconnect - options[:reconnect] = true unless options.has_key?(:reconnect) - @conn.reconnect = options[:reconnect] - end - - rescue => ex - if ex.errno == 1049 # database does not exist. - Logger.info "Database '#{options[:name]}' not found!" - self.class.create(options) - retry - end - raise - end + @conn = Mysql.connect( + options[:address] || 'localhost', + options[:user], + options[:password], + options[:name] + ) + + # You should set recconect to true to avoid MySQL has + # gone away errors. + + if @conn.respond_to? :reconnect + options[:reconnect] = true unless options.has_key?(:reconnect) + @conn.reconnect = options[:reconnect] + end + + rescue => ex + if ex.errno == 1049 # database does not exist. + Logger.info "Database '#{options[:name]}' not found!" + self.class.create(options) + retry + end + raise + end - def close - @conn.close - super - end + def close + @conn.close + super + end - def enchant(klass, manager) - klass.property :oid, Fixnum, :sql => 'integer AUTO_INCREMENT PRIMARY KEY' - super - end - - def query(sql) - Logger.debug sql if $DBG - @conn.query_with_result = true - return @conn.query(sql) - rescue => ex - handle_sql_exception(ex, sql) - end + def enchant(klass, manager) + klass.property :oid, Fixnum, :sql => 'integer AUTO_INCREMENT PRIMARY KEY' + super + end + + def query(sql) + Logger.debug sql if $DBG + @conn.query_with_result = true + return @conn.query(sql) + rescue => ex + handle_sql_exception(ex, sql) + end - def exec(sql) - Logger.debug sql if $DBG - @conn.query_with_result = false - @conn.query(sql) - rescue => ex - handle_sql_exception(ex, sql) - end + def exec(sql) + Logger.debug sql if $DBG + @conn.query_with_result = false + @conn.query(sql) + rescue => ex + handle_sql_exception(ex, sql) + end - def start - # nop - # FIXME: InnoDB supports transactions. - end - - # Commit a transaction. - - def commit - # nop, not supported? - # FIXME: InnoDB supports transactions. - end - - # Rollback a transaction. - - def rollback - # nop, not supported? - # FIXME: InnoDB supports transactions. - end + def start + # nop + # FIXME: InnoDB supports transactions. + end + + # Commit a transaction. + + def commit + # nop, not supported? + # FIXME: InnoDB supports transactions. + end + + # Rollback a transaction. + + def rollback + # nop, not supported? + # FIXME: InnoDB supports transactions. + end - def sql_update(sql) - exec(sql) - @conn.affected_rows - end + def sql_update(sql) + exec(sql) + @conn.affected_rows + end private - def create_table(klass) - fields = fields_for_class(klass) + def create_table(klass) + fields = fields_for_class(klass) - sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}" - - # Create table constrains. - - if klass.__meta and constrains = klass.__meta[:sql_constrain] - sql << ", #{constrains.join(', ')}" - end - - if table_type = @options[:table_type] - sql << ") TYPE = #{table_type};" - else - sql << ");" - end - - # Create indices. - - if klass.__meta and indices = klass.__meta[:index] - for data in indices - idx, options = *data - idx = idx.to_s - pre_sql, post_sql = options[:pre], options[:post] - idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "") - sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});" - end - end - - @conn.query_with_result = false + sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}" + + # Create table constrains. + + if klass.__meta and constrains = klass.__meta[:sql_constrain] + sql << ", #{constrains.join(', ')}" + end + + if table_type = @options[:table_type] + sql << ") TYPE = #{table_type};" + else + sql << ");" + end + + # Create indices. + + if klass.__meta and indices = klass.__meta[:index] + for data in indices + idx, options = *data + idx = idx.to_s + pre_sql, post_sql = options[:pre], options[:post] + idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "") + sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});" + end + end + + @conn.query_with_result = false - begin - @conn.query(sql) - Logger.info "Created table '#{klass::OGTABLE}'." - rescue => ex - if ex.errno == 1050 # table already exists. - Logger.debug 'Table already exists' if $DBG - return - else - raise - end - end - - # Create join tables if needed. Join tables are used in - # 'many_to_many' relations. - - if klass.__meta and join_tables = klass.__meta[:join_tables] - for join_table in join_tables - begin - @conn.query("CREATE TABLE #{join_table} (key1 integer NOT NULL, key2 integer NOT NULL)") - @conn.query("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)") - @conn.query("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)") - rescue => ex - if ex.errno == 1050 # table already exists. - Logger.debug 'Join table already exists' - else - raise - end - end - end - end - end + begin + @conn.query(sql) + Logger.info "Created table '#{klass::OGTABLE}'." + rescue => ex + if ex.errno == 1050 # table already exists. + Logger.debug 'Table already exists' if $DBG + return + else + raise + end + end + + # Create join tables if needed. Join tables are used in + # 'many_to_many' relations. + + if klass.__meta and join_tables = klass.__meta[:join_tables] + for info in join_tables + begin + create_join_table_sql(info).each do |sql| + @conn.query sql + end + rescue => ex + if ex.respond_to?(:errno) and ex.errno == 1050 # table already exists. + Logger.debug 'Join table already exists' if $DBG + else + raise + end + end + end + end + end - def create_field_map(klass) - conn.query_with_result = true - res = @conn.query "SELECT * FROM #{klass::OGTABLE} LIMIT 1" - map = {} + def create_field_map(klass) + conn.query_with_result = true + res = @conn.query "SELECT * FROM #{klass::OGTABLE} LIMIT 1" + map = {} - res.num_fields.times do |i| - map[res.fetch_field.name.intern] = i - end + res.num_fields.times do |i| + map[res.fetch_field.name.intern] = i + end - return map - ensure - res.close if res - end + return map + ensure + res.close if res + end - def write_prop(p) - if p.klass.ancestors.include?(Integer) - return "#\{@#{p.symbol} || 'NULL'\}" - elsif p.klass.ancestors.include?(Float) - return "#\{@#{p.symbol} || 'NULL'\}" - elsif p.klass.ancestors.include?(String) - return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : 'NULL'\}| - elsif p.klass.ancestors.include?(Time) - return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}| - elsif p.klass.ancestors.include?(Date) - return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}| - elsif p.klass.ancestors.include?(TrueClass) - return "#\{@#{p.symbol} ? \"'1'\" : 'NULL' \}" - else - # gmosx: keep the '' for nil symbols. - return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}| - end - end - - def read_prop(p, col) - if p.klass.ancestors.include?(Integer) - return "#{self.class}.parse_int(res[#{col} + offset])" - elsif p.klass.ancestors.include?(Float) - return "#{self.class}.parse_float(res[#{col} + offset])" - elsif p.klass.ancestors.include?(String) - return "res[#{col} + offset]" - elsif p.klass.ancestors.include?(Time) - return "#{self.class}.parse_timestamp(res[#{col} + offset])" - elsif p.klass.ancestors.include?(Date) - return "#{self.class}.parse_date(res[#{col} + offset])" - elsif p.klass.ancestors.include?(TrueClass) - return "('0' != res[#{col} + offset])" - else - return "YAML.load(res[#{col} + offset])" - end - end + def write_prop(p) + if p.klass.ancestors.include?(Integer) + return "#\{@#{p.symbol} || 'NULL'\}" + elsif p.klass.ancestors.include?(Float) + return "#\{@#{p.symbol} || 'NULL'\}" + elsif p.klass.ancestors.include?(String) + return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : 'NULL'\}| + elsif p.klass.ancestors.include?(Time) + return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}| + elsif p.klass.ancestors.include?(Date) + return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}| + elsif p.klass.ancestors.include?(TrueClass) + return "#\{@#{p.symbol} ? \"'1'\" : 'NULL' \}" + else + # gmosx: keep the '' for nil symbols. + return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}| + end + end + + def read_prop(p, col) + if p.klass.ancestors.include?(Integer) + return "#{self.class}.parse_int(res[#{col} + offset])" + elsif p.klass.ancestors.include?(Float) + return "#{self.class}.parse_float(res[#{col} + offset])" + elsif p.klass.ancestors.include?(String) + return "res[#{col} + offset]" + elsif p.klass.ancestors.include?(Time) + return "#{self.class}.parse_timestamp(res[#{col} + offset])" + elsif p.klass.ancestors.include?(Date) + return "#{self.class}.parse_date(res[#{col} + offset])" + elsif p.klass.ancestors.include?(TrueClass) + return "('0' != res[#{col} + offset])" + else + return "YAML.load(res[#{col} + offset])" + end + end - def eval_og_insert(klass) - props = klass.properties.dup - values = props.collect { |p| write_prop(p) }.join(',') - - if klass.metadata.superclass or klass.metadata.subclasses - props << Property.new(:ogtype, String) - values << ", '#{klass}'" - end - - sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})" + def eval_og_insert(klass) + props = klass.properties.dup + values = props.collect { |p| write_prop(p) }.join(',') + + if klass.metadata.superclass or klass.metadata.subclasses + props << Property.new(:ogtype, String) + values << ", '#{klass}'" + end + + sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})" - klass.class_eval %{ - def og_insert(store) - #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)} - store.conn.query_with_result = false - store.conn.query "#{sql}" - @#{klass.pk_symbol} = store.conn.insert_id - #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)} - end - } - end + klass.class_eval %{ + def og_insert(store) + #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)} + store.conn.query_with_result = false + store.conn.query "#{sql}" + @#{klass.pk_symbol} = store.conn.insert_id + #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)} + end + } + end end end