begin 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 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 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 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 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 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 initialize(options) super @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 def close @conn.close super end def enchant(klass, manager) if klass.metadata.primary_key.flatten.first == :oid unless klass.properties.find { |p| p.symbol == :oid } klass.property :oid, Fixnum, :sql => 'integer AUTO_INCREMENT PRIMARY KEY' end end 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 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 private def create_table(klass) fields = fields_for_class(klass) sql = "CREATE TABLE #{klass::OGTABLE} (#{fields.join(', ')}" # Create table constrains. if klass.metadata and constrains = klass.metadata.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 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 = {} res.num_fields.times do |i| map[res.fetch_field.name.intern] = i 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 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| field_for_property(p)}.join(',')}) VALUES (#{values})" klass.class_eval %{ def og_insert(store) #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)} puts "#{sql}" 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