diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2008-06-25 12:12:30 +0100 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2008-06-25 12:12:30 +0100 |
commit | 01571c0fd73a31f78411d6cad6484ddd82ec4778 (patch) | |
tree | d0565f7ad3fd5a65aafbf191170094b90d7ef543 /activerecord/lib | |
parent | b5e9badea281ce0c371ff1c00461febe66f4e2b2 (diff) | |
parent | 670e22e3724791f51d639f409930fba5af920081 (diff) | |
download | rails-01571c0fd73a31f78411d6cad6484ddd82ec4778.tar.gz rails-01571c0fd73a31f78411d6cad6484ddd82ec4778.tar.bz2 rails-01571c0fd73a31f78411d6cad6484ddd82ec4778.zip |
Merge commit 'mainstream/master'
Conflicts:
actionpack/lib/action_view/helpers/javascript_helper.rb
activesupport/lib/active_support/dependencies.rb
activesupport/lib/active_support/inflector.rb
activesupport/lib/active_support/values/time_zone.rb
Diffstat (limited to 'activerecord/lib')
18 files changed, 232 insertions, 1723 deletions
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 087ed2a587..194c4ee5db 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -103,10 +103,10 @@ module ActiveRecord associated_records = reflection.klass.find(:all, :conditions => [conditions, ids], :include => options[:include], :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} as t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}", - :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as _parent_record_id", + :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id", :order => options[:order]) - set_association_collection_records(id_to_record_map, reflection.name, associated_records, '_parent_record_id') + set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id') end def preload_has_one_association(records, reflection, preload_options={}) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 9479296b90..d2253cb61c 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -691,6 +691,7 @@ module ActiveRecord # association is a polymorphic +belongs_to+. # * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>. # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association. + # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. true by default. # # Option examples: # has_many :comments, :order => "posted_on" @@ -711,7 +712,7 @@ module ActiveRecord configure_dependency_for_has_many(reflection) - add_multiple_associated_save_callbacks(reflection.name) + add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false add_association_callbacks(reflection.name, reflection.options) if options[:through] @@ -770,6 +771,7 @@ module ActiveRecord # * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_one :through</tt> queries where the source # association is a polymorphic +belongs_to+. # * <tt>:readonly</tt> - If true, the associated object is readonly through the association. + # * <tt>:validate</tt> - If false, don't validate the associated object when saving the parent object. +false+ by default. # # Option examples: # has_one :credit_card, :dependent => :destroy # destroys the associated credit card @@ -800,7 +802,7 @@ module ActiveRecord end after_save method_name - add_single_associated_save_callbacks(reflection.name) + add_single_associated_save_callbacks(reflection.name) if options[:validate] == true association_accessor_methods(reflection, HasOneAssociation) association_constructor_method(:build, reflection, HasOneAssociation) association_constructor_method(:create, reflection, HasOneAssociation) @@ -858,6 +860,7 @@ module ActiveRecord # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>). # * <tt>:readonly</tt> - If true, the associated object is readonly through the association. + # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +false+ by default. # # Option examples: # belongs_to :firm, :foreign_key => "client_of" @@ -938,6 +941,8 @@ module ActiveRecord ) end + add_single_associated_save_callbacks(reflection.name) if options[:validate] == true + configure_dependency_for_belongs_to(reflection) end @@ -1026,6 +1031,7 @@ module ActiveRecord # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error. # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association. + # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +true+ by default. # # Option examples: # has_and_belongs_to_many :projects @@ -1038,7 +1044,7 @@ module ActiveRecord def has_and_belongs_to_many(association_id, options = {}, &extension) reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension) - add_multiple_associated_save_callbacks(reflection.name) + add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false collection_accessor_methods(reflection, HasAndBelongsToManyAssociation) # Don't use a before_destroy callback since users' before_destroy @@ -1344,7 +1350,8 @@ module ActiveRecord :uniq, :finder_sql, :counter_sql, :before_add, :after_add, :before_remove, :after_remove, - :extend, :readonly + :extend, :readonly, + :validate ) options[:extend] = create_extension_modules(association_id, extension, options[:extend]) @@ -1354,7 +1361,7 @@ module ActiveRecord def create_has_one_reflection(association_id, options) options.assert_valid_keys( - :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly + :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate ) create_reflection(:has_one, association_id, options, self) @@ -1362,7 +1369,7 @@ module ActiveRecord def create_has_one_through_reflection(association_id, options) options.assert_valid_keys( - :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type + :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate ) create_reflection(:has_one, association_id, options, self) end @@ -1370,7 +1377,7 @@ module ActiveRecord def create_belongs_to_reflection(association_id, options) options.assert_valid_keys( :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent, - :counter_cache, :extend, :polymorphic, :readonly + :counter_cache, :extend, :polymorphic, :readonly, :validate ) reflection = create_reflection(:belongs_to, association_id, options, self) @@ -1389,7 +1396,8 @@ module ActiveRecord :uniq, :finder_sql, :delete_sql, :insert_sql, :before_add, :after_add, :before_remove, :after_remove, - :extend, :readonly + :extend, :readonly, + :validate ) options[:extend] = create_extension_modules(association_id, extension, options[:extend]) @@ -1507,7 +1515,7 @@ module ActiveRecord end def order_tables(options) - order = options[:order] + order = [options[:order], scope(:find, :order) ].join(", ") return [] unless order && order.is_a?(String) order.scan(/([\.\w]+).?\./).flatten end @@ -1639,7 +1647,9 @@ module ActiveRecord end def join_for_table_name(table_name) - @joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil + join = (@joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first) rescue nil + return join unless join.nil? + @joins.select{|j|j.is_a?(JoinAssociation) && j.aliased_join_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil end def joins_for_table_name(table_name) diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index 4fa8e9d0a8..918404eac6 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -87,6 +87,7 @@ module ActiveRecord :joins => @join_sql, :readonly => false, :order => @reflection.options[:order], + :include => @reflection.options[:include], :limit => @reflection.options[:limit] } } end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index f584a97cbb..295beb2966 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -100,7 +100,7 @@ module ActiveRecord create_scoping = {} set_belongs_to_association_for(create_scoping) { - :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit] }, + :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]}, :create => create_scoping } end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index cfc6a5693a..c2a8d3ec3d 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -372,7 +372,7 @@ module ActiveRecord #:nodoc: def self.reset_subclasses #:nodoc: nonreloadables = [] subclasses.each do |klass| - unless Dependencies.autoloaded? klass + unless ActiveSupport::Dependencies.autoloaded? klass nonreloadables << klass next end @@ -1296,6 +1296,20 @@ module ActiveRecord #:nodoc: store_full_sti_class ? name : name.demodulize end + # Merges conditions so that the result is a valid +condition+ + def merge_conditions(*conditions) + segments = [] + + conditions.each do |condition| + unless condition.blank? + sql = sanitize_sql(condition) + segments << sql unless sql.blank? + end + end + + "(#{segments.join(') AND (')})" unless segments.empty? + end + private def find_initial(options) options.update(:limit => 1) @@ -1464,7 +1478,7 @@ module ActiveRecord #:nodoc: def construct_finder_sql(options) scope = scope(:find) - sql = "SELECT #{options[:select] || (scope && scope[:select]) || (options[:joins] && quoted_table_name + '.*') || '*'} " + sql = "SELECT #{options[:select] || (scope && scope[:select]) || ((options[:joins] || (scope && scope[:joins])) && quoted_table_name + '.*') || '*'} " sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} " add_joins!(sql, options, scope) @@ -1483,20 +1497,6 @@ module ActiveRecord #:nodoc: (safe_to_array(first) + safe_to_array(second)).uniq end - # Merges conditions so that the result is a valid +condition+ - def merge_conditions(*conditions) - segments = [] - - conditions.each do |condition| - unless condition.blank? - sql = sanitize_sql(condition) - segments << sql unless sql.blank? - end - end - - "(#{segments.join(') AND (')})" unless segments.empty? - end - # Object#to_a is deprecated, though it does have the desired behavior def safe_to_array(o) case o @@ -1902,10 +1902,12 @@ module ActiveRecord #:nodoc: # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass. def compute_type(type_name) modularized_name = type_name_with_module(type_name) - begin - class_eval(modularized_name, __FILE__, __LINE__) - rescue NameError - class_eval(type_name, __FILE__, __LINE__) + silence_warnings do + begin + class_eval(modularized_name, __FILE__, __LINE__) + rescue NameError + class_eval(type_name, __FILE__, __LINE__) + end end end @@ -2052,9 +2054,10 @@ module ActiveRecord #:nodoc: end def replace_named_bind_variables(statement, bind_vars) #:nodoc: - statement.gsub(/:([a-zA-Z]\w*)/) do - match = $1.to_sym - if bind_vars.include?(match) + statement.gsub(/(:?):([a-zA-Z]\w*)/) do + if $1 == ':' # skip postgresql casts + $& # return the whole match + elsif bind_vars.include?(match = $2.to_sym) quote_bound_value(bind_vars[match]) else raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}" @@ -2063,13 +2066,18 @@ module ActiveRecord #:nodoc: end def expand_range_bind_variables(bind_vars) #:nodoc: - bind_vars.sum do |var| + expanded = [] + + bind_vars.each do |var| if var.is_a?(Range) - [var.first, var.last] + expanded << var.first + expanded << var.last else - [var] + expanded << var end end + + expanded end def quote_bound_value(value) #:nodoc: @@ -2161,11 +2169,11 @@ module ActiveRecord #:nodoc: def cache_key case when new_record? - "#{self.class.name.tableize}/new" - when self[:updated_at] - "#{self.class.name.tableize}/#{id}-#{updated_at.to_s(:number)}" + "#{self.class.model_name.cache_key}/new" + when timestamp = self[:updated_at] + "#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}" else - "#{self.class.name.tableize}/#{id}" + "#{self.class.model_name.cache_key}/#{id}" end end @@ -2246,12 +2254,12 @@ module ActiveRecord #:nodoc: end end - # Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records. - # Note: This method is overwritten by the Validation module that'll make sure that updates made with this method - # aren't subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid. + # Updates a single attribute and saves the record without going through the normal validation procedure. + # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method + # in Base is replaced with this when the validations module is mixed in, which it is by default. def update_attribute(name, value) send(name.to_s + '=', value) - save + save(false) end # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 10e8330d1c..2ca1a0aaa3 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -1,6 +1,6 @@ module ActiveRecord module Calculations #:nodoc: - CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include] + CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include, :from] def self.included(base) base.extend(ClassMethods) end @@ -27,6 +27,8 @@ module ActiveRecord # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not # include the joined columns. # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ... + # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name + # of a database view). # # Examples for counting all: # Person.count # returns the total count of all people @@ -71,7 +73,7 @@ module ActiveRecord # # Person.sum('age') def sum(column_name, options = {}) - calculate(:sum, column_name, options) || 0 + calculate(:sum, column_name, options) end # This calculates aggregate values in the given column. Methods for count, sum, average, minimum, and maximum have been added as shortcuts. @@ -178,8 +180,12 @@ module ActiveRecord sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group] - sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround - sql << " FROM #{connection.quote_table_name(table_name)} " + if options[:from] + sql << " FROM #{options[:from]} " + else + sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround + sql << " FROM #{connection.quote_table_name(table_name)} " + end if merged_includes.any? join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins]) sql << join_dependency.join_associations.collect{|join| join.association_join }.join @@ -266,6 +272,7 @@ module ActiveRecord operation = operation.to_s.downcase case operation when 'count' then value.to_i + when 'sum' then value =~ /\./ ? value.to_f : value.to_i when 'avg' then value && value.to_f else column ? column.type_cast(value) : value end diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 6c2f65df05..5883cdfbe1 100755 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -293,14 +293,14 @@ module ActiveRecord private def callback(method) - notify(method) - result = run_callbacks(method) { |result, object| result == false } if result != false && respond_to_without_attributes?(method) result = send(method) end + notify(method) + return result end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index a2a1bb8c82..0f60a91ef1 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -331,21 +331,32 @@ module ActiveRecord end def assume_migrated_upto_version(version) + version = version.to_i sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name) + migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i) versions = Dir['db/migrate/[0-9]*_*.rb'].map do |filename| filename.split('/').last.split('_').first.to_i end - execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" unless migrated.include?(version.to_i) - (versions - migrated).select { |v| v < version.to_i }.each do |v| - execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" + unless migrated.include?(version) + execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" + end + + inserted = Set.new + (versions - migrated).each do |v| + if inserted.include?(v) + raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict." + elsif v < version + execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" + inserted << v + end end end def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: if native = native_database_types[type] - column_type_sql = native.is_a?(Hash) ? native[:name] : native + column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup if type == :decimal # ignore limit, use precision and scale scale ||= native[:scale] @@ -360,7 +371,7 @@ module ActiveRecord raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified" end - elsif limit ||= native.is_a?(Hash) && native[:limit] + elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit]) column_type_sql << "(#{limit})" end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 653b45021d..dd54950790 100755 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -42,30 +42,6 @@ end module ActiveRecord class Base - def self.require_mysql - # Include the MySQL driver if one hasn't already been loaded - unless defined? Mysql - begin - require_library_or_gem 'mysql' - rescue LoadError => cannot_require_mysql - # Use the bundled Ruby/MySQL driver if no driver is already in place - begin - ActiveRecord::Base.logger.info( - "WARNING: You're using the Ruby-based MySQL library that ships with Rails. This library is not suited for production. " + - "Please install the C-based MySQL library instead (gem install mysql)." - ) if ActiveRecord::Base.logger - - require 'active_record/vendor/mysql' - rescue LoadError - raise cannot_require_mysql - end - end - end - - # Define Mysql::Result.all_hashes - MysqlCompat.define_all_hashes_method! - end - # Establishes a connection to the database that's used by all Active Record objects. def self.mysql_connection(config) # :nodoc: config = config.symbolize_keys @@ -81,7 +57,17 @@ module ActiveRecord raise ArgumentError, "No database specified. Missing argument: database." end - require_mysql + # Require the MySQL driver and define Mysql::Result.all_hashes + unless defined? Mysql + begin + require_library_or_gem('mysql') + rescue LoadError + $stderr.puts '!!! The bundled mysql.rb driver has been removed from Rails 2.2. Please install the mysql gem and try again: gem install mysql.' + raise + end + end + MysqlCompat.define_all_hashes_method! + mysql = Mysql.init mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey] @@ -113,7 +99,8 @@ module ActiveRecord end def extract_limit(sql_type) - if sql_type =~ /blob|text/i + case sql_type + when /blob|text/i case sql_type when /tiny/i 255 @@ -124,6 +111,11 @@ module ActiveRecord else super # we could return 65535 here, but we leave it undecorated by default end + when /^bigint/i; 8 + when /^int/i; 4 + when /^mediumint/i; 3 + when /^smallint/i; 2 + when /^tinyint/i; 1 else super end @@ -165,8 +157,10 @@ module ActiveRecord # # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false class MysqlAdapter < AbstractAdapter - @@emulate_booleans = true cattr_accessor :emulate_booleans + self.emulate_booleans = true + + ADAPTER_NAME = 'MySQL'.freeze LOST_CONNECTION_ERROR_MESSAGES = [ "Server shutdown in progress", @@ -174,7 +168,22 @@ module ActiveRecord "Lost connection to MySQL server during query", "MySQL server has gone away" ] - QUOTED_TRUE, QUOTED_FALSE = '1', '0' + QUOTED_TRUE, QUOTED_FALSE = '1'.freeze, '0'.freeze + + NATIVE_DATABASE_TYPES = { + :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze, + :string => { :name => "varchar", :limit => 255 }, + :text => { :name => "text" }, + :integer => { :name => "int", :limit => 4 }, + :float => { :name => "float" }, + :decimal => { :name => "decimal" }, + :datetime => { :name => "datetime" }, + :timestamp => { :name => "datetime" }, + :time => { :name => "time" }, + :date => { :name => "date" }, + :binary => { :name => "blob" }, + :boolean => { :name => "tinyint", :limit => 1 } + } def initialize(connection, logger, connection_options, config) super(connection, logger) @@ -184,7 +193,7 @@ module ActiveRecord end def adapter_name #:nodoc: - 'MySQL' + ADAPTER_NAME end def supports_migrations? #:nodoc: @@ -192,20 +201,7 @@ module ActiveRecord end def native_database_types #:nodoc: - { - :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY", - :string => { :name => "varchar", :limit => 255 }, - :text => { :name => "text" }, - :integer => { :name => "int"}, - :float => { :name => "float" }, - :decimal => { :name => "decimal" }, - :datetime => { :name => "datetime" }, - :timestamp => { :name => "datetime" }, - :time => { :name => "time" }, - :date => { :name => "date" }, - :binary => { :name => "blob" }, - :boolean => { :name => "tinyint", :limit => 1 } - } + NATIVE_DATABASE_TYPES end @@ -460,8 +456,16 @@ module ActiveRecord end def rename_column(table_name, column_name, new_column_name) #:nodoc: + options = {} + if column = columns(table_name).find { |c| c.name == column_name.to_s } + options[:default] = column.default + else + raise ActiveRecordError, "No such column: #{table_name}.#{column_name}" + end current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"] - execute "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}" + rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}" + add_column_options!(rename_column_sql, options) + execute(rename_column_sql) end # Maps logical Rails types to MySQL-specific data types. @@ -469,14 +473,11 @@ module ActiveRecord return super unless type.to_s == 'integer' case limit - when 0..3 - "smallint(#{limit})" - when 4..8 - "int(#{limit})" - when 9..20 - "bigint(#{limit})" - else - 'int(11)' + when 1; 'tinyint' + when 2; 'smallint' + when 3; 'mediumint' + when 4, nil; 'int(11)' + else; 'bigint' end end @@ -498,12 +499,17 @@ module ActiveRecord private def connect + @connection.reconnect = true if @connection.respond_to?(:reconnect=) + encoding = @config[:encoding] if encoding @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil end + @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey] + @connection.real_connect(*@connection_options) + execute("SET NAMES '#{encoding}'") if encoding # By default, MySQL 'where id is null' selects the last inserted id. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 7dc7398b0a..88f703d813 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -47,6 +47,12 @@ module ActiveRecord end private + def extract_limit(sql_type) + return 8 if sql_type =~ /^bigint/i + return 2 if sql_type =~ /^smallint/i + super + end + # Extracts the scale from PostgreSQL-specific data types. def extract_scale(sql_type) # Money type has a fixed scale of 2. @@ -238,9 +244,26 @@ module ActiveRecord # * <tt>:min_messages</tt> - An optional client min messages that is used in a <tt>SET client_min_messages TO <min_messages></tt> call on the connection. # * <tt>:allow_concurrency</tt> - If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods. class PostgreSQLAdapter < AbstractAdapter + ADAPTER_NAME = 'PostgreSQL'.freeze + + NATIVE_DATABASE_TYPES = { + :primary_key => "serial primary key".freeze, + :string => { :name => "character varying", :limit => 255 }, + :text => { :name => "text" }, + :integer => { :name => "integer" }, + :float => { :name => "float" }, + :decimal => { :name => "decimal" }, + :datetime => { :name => "timestamp" }, + :timestamp => { :name => "timestamp" }, + :time => { :name => "time" }, + :date => { :name => "date" }, + :binary => { :name => "bytea" }, + :boolean => { :name => "boolean" } + } + # Returns 'PostgreSQL' as adapter name for identification purposes. def adapter_name - 'PostgreSQL' + ADAPTER_NAME end # Initializes and connects a PostgreSQL adapter. @@ -282,20 +305,7 @@ module ActiveRecord end def native_database_types #:nodoc: - { - :primary_key => "serial primary key", - :string => { :name => "character varying", :limit => 255 }, - :text => { :name => "text" }, - :integer => { :name => "integer" }, - :float => { :name => "float" }, - :decimal => { :name => "decimal" }, - :datetime => { :name => "timestamp" }, - :timestamp => { :name => "timestamp" }, - :time => { :name => "time" }, - :date => { :name => "date" }, - :binary => { :name => "bytea" }, - :boolean => { :name => "boolean" } - } + NATIVE_DATABASE_TYPES end # Does PostgreSQL support migrations? @@ -319,6 +329,10 @@ module ActiveRecord has_support end + def supports_insert_with_returning? + postgresql_version >= 80200 + end + # Returns the configured supported identifier length supported by PostgreSQL, # or report the default of 63 on PostgreSQL 7.x. def table_alias_length @@ -411,8 +425,34 @@ module ActiveRecord # Executes an INSERT query and returns the new record's ID def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) + # Extract the table from the insert sql. Yuck. table = sql.split(" ", 4)[2].gsub('"', '') - super || pk && last_insert_id(table, sequence_name || default_sequence_name(table, pk)) + + # Try an insert with 'returning id' if available (PG >= 8.2) + if supports_insert_with_returning? + pk, sequence_name = *pk_and_sequence_for(table) unless pk + if pk + id = select_value("#{sql} RETURNING #{quote_column_name(pk)}") + clear_query_cache + return id + end + end + + # Otherwise, insert then grab last_insert_id. + if insert_id = super + insert_id + else + # If neither pk nor sequence name is given, look them up. + unless pk || sequence_name + pk, sequence_name = *pk_and_sequence_for(table) + end + + # If a pk is given, fallback to default sequence name. + # Don't fetch last insert id for a table without a pk. + if pk && sequence_name ||= default_sequence_name(table, pk) + last_insert_id(table, sequence_name) + end + end end # create a 2D array representing the result set @@ -506,7 +546,7 @@ module ActiveRecord end end - execute "CREATE DATABASE #{name}#{option_string}" + execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}" end # Drops a PostgreSQL database @@ -514,7 +554,15 @@ module ActiveRecord # Example: # drop_database 'matt_development' def drop_database(name) #:nodoc: - execute "DROP DATABASE IF EXISTS #{name}" + if postgresql_version >= 80200 + execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}" + else + begin + execute "DROP DATABASE #{quote_table_name(name)}" + rescue ActiveRecord::StatementInvalid + @logger.warn "#{name} database doesn't exist." if @logger + end + end end @@ -676,7 +724,7 @@ module ActiveRecord # Renames a table. def rename_table(name, new_name) - execute "ALTER TABLE #{name} RENAME TO #{new_name}" + execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" end # Adds a new column to the named table. @@ -743,12 +791,10 @@ module ActiveRecord def type_to_sql(type, limit = nil, precision = nil, scale = nil) return super unless type.to_s == 'integer' - if limit.nil? || limit == 4 - 'integer' - elsif limit < 4 - 'smallint' - else - 'bigint' + case limit + when 1..2; 'smallint' + when 3..4, nil; 'integer' + when 5..8; 'bigint' end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index c4cbe5d52f..e19614e31f 100755 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -547,7 +547,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash) @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter @class_name = class_name || (ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize) - @table_name = ActiveRecord::Base.table_name_prefix + @table_name + ActiveRecord::Base.table_name_suffix + @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}" @table_name = class_name.table_name if class_name.respond_to?(:table_name) @connection = class_name.connection if class_name.respond_to?(:connection) read_fixture_files diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index c66034d18b..ff9899d032 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -68,6 +68,7 @@ module ActiveRecord def update_with_lock(attribute_names = @attributes.keys) #:nodoc: return update_without_lock(attribute_names) unless locking_enabled? + return 0 if attribute_names.empty? lock_col = self.class.locking_column previous_value = send(lock_col).to_i diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index b47b01e99a..e095b3c766 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -399,7 +399,10 @@ module ActiveRecord def run target = migrations.detect { |m| m.version == @target_version } raise UnknownMigrationVersionError.new(@target_version) if target.nil? - target.migrate(@direction) + unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i)) + target.migrate(@direction) + record_version_state_after_migrating(target.version) + end end def migrate diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index b0c8a8b815..eac61e9e43 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -82,6 +82,7 @@ module ActiveRecord # expected_options = { :conditions => { :colored => 'red' } } # assert_equal expected_options, Shirt.colored('red').proxy_options def named_scope(name, options = {}, &block) + name = name.to_sym scopes[name] = lambda do |parent_scope, *args| Scope.new(parent_scope, case options when Hash diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb index 6e55e36b7d..25e0e61c69 100644 --- a/activerecord/lib/active_record/observer.rb +++ b/activerecord/lib/active_record/observer.rb @@ -189,7 +189,7 @@ module ActiveRecord def add_observer!(klass) klass.add_observer(self) - klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find) + klass.class_eval 'def after_find() end' unless klass.method_defined?(:after_find) end end end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 52805ea851..8196442fe5 100755 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -277,7 +277,6 @@ module ActiveRecord base.class_eval do alias_method_chain :save, :validation alias_method_chain :save!, :validation - alias_method_chain :update_attribute, :validation_skipping end base.send :include, ActiveSupport::Callbacks @@ -914,14 +913,6 @@ module ActiveRecord end end - # Updates a single attribute and saves the record without going through the normal validation procedure. - # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method - # in Base is replaced with this when the validations module is mixed in, which it is by default. - def update_attribute_with_validation_skipping(name, value) - send(name.to_s + '=', value) - save(false) - end - # Runs +validate+ and +validate_on_create+ or +validate_on_update+ and returns true if no errors were added otherwise false. def valid? errors.clear diff --git a/activerecord/lib/active_record/vendor/db2.rb b/activerecord/lib/active_record/vendor/db2.rb deleted file mode 100644 index 812c8cc517..0000000000 --- a/activerecord/lib/active_record/vendor/db2.rb +++ /dev/null @@ -1,362 +0,0 @@ -require 'db2/db2cli.rb' - -module DB2 - module DB2Util - include DB2CLI - - def free() SQLFreeHandle(@handle_type, @handle); end - def handle() @handle; end - - def check_rc(rc) - if ![SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NO_DATA_FOUND].include?(rc) - rec = 1 - msg = '' - loop do - a = SQLGetDiagRec(@handle_type, @handle, rec, 500) - break if a[0] != SQL_SUCCESS - msg << a[3] if !a[3].nil? and a[3] != '' # Create message. - rec += 1 - end - raise "DB2 error: #{msg}" - end - end - end - - class Environment - include DB2Util - - def initialize - @handle_type = SQL_HANDLE_ENV - rc, @handle = SQLAllocHandle(@handle_type, SQL_NULL_HANDLE) - check_rc(rc) - end - - def data_sources(buffer_length = 1024) - retval = [] - max_buffer_length = buffer_length - - a = SQLDataSources(@handle, SQL_FETCH_FIRST, SQL_MAX_DSN_LENGTH + 1, buffer_length) - retval << [a[1], a[3]] - max_buffer_length = [max_buffer_length, a[4]].max - - loop do - a = SQLDataSources(@handle, SQL_FETCH_NEXT, SQL_MAX_DSN_LENGTH + 1, buffer_length) - break if a[0] == SQL_NO_DATA_FOUND - - retval << [a[1], a[3]] - max_buffer_length = [max_buffer_length, a[4]].max - end - - if max_buffer_length > buffer_length - get_data_sources(max_buffer_length) - else - retval - end - end - end - - class Connection - include DB2Util - - def initialize(environment) - @env = environment - @handle_type = SQL_HANDLE_DBC - rc, @handle = SQLAllocHandle(@handle_type, @env.handle) - check_rc(rc) - end - - def connect(server_name, user_name = '', auth = '') - check_rc(SQLConnect(@handle, server_name, user_name.to_s, auth.to_s)) - end - - def set_connect_attr(attr, value) - value += "\0" if value.class == String - check_rc(SQLSetConnectAttr(@handle, attr, value)) - end - - def set_auto_commit_on - set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_ON) - end - - def set_auto_commit_off - set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF) - end - - def disconnect - check_rc(SQLDisconnect(@handle)) - end - - def rollback - check_rc(SQLEndTran(@handle_type, @handle, SQL_ROLLBACK)) - end - - def commit - check_rc(SQLEndTran(@handle_type, @handle, SQL_COMMIT)) - end - end - - class Statement - include DB2Util - - def initialize(connection) - @conn = connection - @handle_type = SQL_HANDLE_STMT - @parms = [] #yun - @sql = '' #yun - @numParms = 0 #yun - @prepared = false #yun - @parmArray = [] #yun. attributes of the parameter markers - rc, @handle = SQLAllocHandle(@handle_type, @conn.handle) - check_rc(rc) - end - - def columns(table_name, schema_name = '%') - check_rc(SQLColumns(@handle, '', schema_name.upcase, table_name.upcase, '%')) - fetch_all - end - - def tables(schema_name = '%') - check_rc(SQLTables(@handle, '', schema_name.upcase, '%', 'TABLE')) - fetch_all - end - - def indexes(table_name, schema_name = '') - check_rc(SQLStatistics(@handle, '', schema_name.upcase, table_name.upcase, SQL_INDEX_ALL, SQL_ENSURE)) - fetch_all - end - - def prepare(sql) - @sql = sql - check_rc(SQLPrepare(@handle, sql)) - rc, @numParms = SQLNumParams(@handle) #number of question marks - check_rc(rc) - #-------------------------------------------------------------------------- - # parameter attributes are stored in instance variable @parmArray so that - # they are available when execute method is called. - #-------------------------------------------------------------------------- - if @numParms > 0 # get parameter marker attributes - 1.upto(@numParms) do |i| # parameter number starts from 1 - rc, type, size, decimalDigits = SQLDescribeParam(@handle, i) - check_rc(rc) - @parmArray << Parameter.new(type, size, decimalDigits) - end - end - @prepared = true - self - end - - def execute(*parms) - raise "The statement was not prepared" if @prepared == false - - if parms.size == 1 and parms[0].class == Array - parms = parms[0] - end - - if @numParms != parms.size - raise "Number of parameters supplied does not match with the SQL statement" - end - - if @numParms > 0 #need to bind parameters - #-------------------------------------------------------------------- - #calling bindParms may not be safe. Look comment below. - #-------------------------------------------------------------------- - #bindParms(parms) - - valueArray = [] - 1.upto(@numParms) do |i| # parameter number starts from 1 - type = @parmArray[i - 1].class - size = @parmArray[i - 1].size - decimalDigits = @parmArray[i - 1].decimalDigits - - if parms[i - 1].class == String - valueArray << parms[i - 1] - else - valueArray << parms[i - 1].to_s - end - - rc = SQLBindParameter(@handle, i, type, size, decimalDigits, valueArray[i - 1]) - check_rc(rc) - end - end - - check_rc(SQLExecute(@handle)) - - if @numParms != 0 - check_rc(SQLFreeStmt(@handle, SQL_RESET_PARAMS)) # Reset parameters - end - - self - end - - #------------------------------------------------------------------------------- - # The last argument(value) to SQLBindParameter is a deferred argument, that is, - # it should be available when SQLExecute is called. Even though "value" is - # local to bindParms method, it seems that it is available when SQLExecute - # is called. I am not sure whether it would still work if garbage collection - # is done between bindParms call and SQLExecute call inside the execute method - # above. - #------------------------------------------------------------------------------- - def bindParms(parms) # This is the real thing. It uses SQLBindParms - 1.upto(@numParms) do |i| # parameter number starts from 1 - rc, dataType, parmSize, decimalDigits = SQLDescribeParam(@handle, i) - check_rc(rc) - if parms[i - 1].class == String - value = parms[i - 1] - else - value = parms[i - 1].to_s - end - rc = SQLBindParameter(@handle, i, dataType, parmSize, decimalDigits, value) - check_rc(rc) - end - end - - #------------------------------------------------------------------------------ - # bind method does not use DB2's SQLBindParams, but replaces "?" in the - # SQL statement with the value before passing the SQL statement to DB2. - # It is not efficient and can handle only strings since it puts everything in - # quotes. - #------------------------------------------------------------------------------ - def bind(sql, args) #does not use SQLBindParams - arg_index = 0 - result = "" - tokens(sql).each do |part| - case part - when '?' - result << "'" + (args[arg_index]) + "'" #put it into quotes - arg_index += 1 - when '??' - result << "?" - else - result << part - end - end - if arg_index < args.size - raise "Too many SQL parameters" - elsif arg_index > args.size - raise "Not enough SQL parameters" - end - result - end - - ## Break the sql string into parts. - # - # This is NOT a full lexer for SQL. It just breaks up the SQL - # string enough so that question marks, double question marks and - # quoted strings are separated. This is used when binding - # arguments to "?" in the SQL string. Note: comments are not - # handled. - # - def tokens(sql) - toks = sql.scan(/('([^'\\]|''|\\.)*'|"([^"\\]|""|\\.)*"|\?\??|[^'"?]+)/) - toks.collect { |t| t[0] } - end - - def exec_direct(sql) - check_rc(SQLExecDirect(@handle, sql)) - self - end - - def set_cursor_name(name) - check_rc(SQLSetCursorName(@handle, name)) - self - end - - def get_cursor_name - rc, name = SQLGetCursorName(@handle) - check_rc(rc) - name - end - - def row_count - rc, rowcount = SQLRowCount(@handle) - check_rc(rc) - rowcount - end - - def num_result_cols - rc, cols = SQLNumResultCols(@handle) - check_rc(rc) - cols - end - - def fetch_all - if block_given? - while row = fetch do - yield row - end - else - res = [] - while row = fetch do - res << row - end - res - end - end - - def fetch - cols = get_col_desc - rc = SQLFetch(@handle) - if rc == SQL_NO_DATA_FOUND - SQLFreeStmt(@handle, SQL_CLOSE) # Close cursor - SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters - return nil - end - raise "ERROR" unless rc == SQL_SUCCESS - - retval = [] - cols.each_with_index do |c, i| - rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1) #yun added 1 to c[2] - retval << adjust_content(content) - end - retval - end - - def fetch_as_hash - cols = get_col_desc - rc = SQLFetch(@handle) - if rc == SQL_NO_DATA_FOUND - SQLFreeStmt(@handle, SQL_CLOSE) # Close cursor - SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters - return nil - end - raise "ERROR" unless rc == SQL_SUCCESS - - retval = {} - cols.each_with_index do |c, i| - rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1) #yun added 1 to c[2] - retval[c[0]] = adjust_content(content) - end - retval - end - - def get_col_desc - rc, nr_cols = SQLNumResultCols(@handle) - cols = (1..nr_cols).collect do |c| - rc, name, bl, type, col_sz = SQLDescribeCol(@handle, c, 1024) - [name.downcase, type, col_sz] - end - end - - def adjust_content(c) - case c.class.to_s - when 'DB2CLI::NullClass' - return nil - when 'DB2CLI::Time' - "%02d:%02d:%02d" % [c.hour, c.minute, c.second] - when 'DB2CLI::Date' - "%04d-%02d-%02d" % [c.year, c.month, c.day] - when 'DB2CLI::Timestamp' - "%04d-%02d-%02d %02d:%02d:%02d" % [c.year, c.month, c.day, c.hour, c.minute, c.second] - else - return c - end - end - end - - class Parameter - attr_reader :type, :size, :decimalDigits - def initialize(type, size, decimalDigits) - @type, @size, @decimalDigits = type, size, decimalDigits - end - end -end diff --git a/activerecord/lib/active_record/vendor/mysql.rb b/activerecord/lib/active_record/vendor/mysql.rb deleted file mode 100644 index 1c3294c719..0000000000 --- a/activerecord/lib/active_record/vendor/mysql.rb +++ /dev/null @@ -1,1214 +0,0 @@ -# $Id: mysql.rb,v 1.24 2005/02/12 11:37:15 tommy Exp $ -# -# Copyright (C) 2003-2005 TOMITA Masahiro -# tommy@tmtm.org -# - -class Mysql - - VERSION = "4.0-ruby-0.2.6-plus-changes" - - require "socket" - require "digest/sha1" - - MAX_PACKET_LENGTH = 256*256*256-1 - MAX_ALLOWED_PACKET = 1024*1024*1024 - - MYSQL_UNIX_ADDR = "/tmp/mysql.sock" - MYSQL_PORT = 3306 - PROTOCOL_VERSION = 10 - - SCRAMBLE_LENGTH = 20 - SCRAMBLE_LENGTH_323 = 8 - - # Command - COM_SLEEP = 0 - COM_QUIT = 1 - COM_INIT_DB = 2 - COM_QUERY = 3 - COM_FIELD_LIST = 4 - COM_CREATE_DB = 5 - COM_DROP_DB = 6 - COM_REFRESH = 7 - COM_SHUTDOWN = 8 - COM_STATISTICS = 9 - COM_PROCESS_INFO = 10 - COM_CONNECT = 11 - COM_PROCESS_KILL = 12 - COM_DEBUG = 13 - COM_PING = 14 - COM_TIME = 15 - COM_DELAYED_INSERT = 16 - COM_CHANGE_USER = 17 - COM_BINLOG_DUMP = 18 - COM_TABLE_DUMP = 19 - COM_CONNECT_OUT = 20 - COM_REGISTER_SLAVE = 21 - - # Client flag - CLIENT_LONG_PASSWORD = 1 - CLIENT_FOUND_ROWS = 1 << 1 - CLIENT_LONG_FLAG = 1 << 2 - CLIENT_CONNECT_WITH_DB= 1 << 3 - CLIENT_NO_SCHEMA = 1 << 4 - CLIENT_COMPRESS = 1 << 5 - CLIENT_ODBC = 1 << 6 - CLIENT_LOCAL_FILES = 1 << 7 - CLIENT_IGNORE_SPACE = 1 << 8 - CLIENT_PROTOCOL_41 = 1 << 9 - CLIENT_INTERACTIVE = 1 << 10 - CLIENT_SSL = 1 << 11 - CLIENT_IGNORE_SIGPIPE = 1 << 12 - CLIENT_TRANSACTIONS = 1 << 13 - CLIENT_RESERVED = 1 << 14 - CLIENT_SECURE_CONNECTION = 1 << 15 - CLIENT_CAPABILITIES = CLIENT_LONG_PASSWORD|CLIENT_LONG_FLAG|CLIENT_TRANSACTIONS - PROTO_AUTH41 = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION - - # Connection Option - OPT_CONNECT_TIMEOUT = 0 - OPT_COMPRESS = 1 - OPT_NAMED_PIPE = 2 - INIT_COMMAND = 3 - READ_DEFAULT_FILE = 4 - READ_DEFAULT_GROUP = 5 - SET_CHARSET_DIR = 6 - SET_CHARSET_NAME = 7 - OPT_LOCAL_INFILE = 8 - - # Server Status - SERVER_STATUS_IN_TRANS = 1 - SERVER_STATUS_AUTOCOMMIT = 2 - - # Refresh parameter - REFRESH_GRANT = 1 - REFRESH_LOG = 2 - REFRESH_TABLES = 4 - REFRESH_HOSTS = 8 - REFRESH_STATUS = 16 - REFRESH_THREADS = 32 - REFRESH_SLAVE = 64 - REFRESH_MASTER = 128 - - def initialize(*args) - @client_flag = 0 - @max_allowed_packet = MAX_ALLOWED_PACKET - @query_with_result = true - @status = :STATUS_READY - if args[0] != :INIT then - real_connect(*args) - end - end - - def real_connect(host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=nil) - @server_status = SERVER_STATUS_AUTOCOMMIT - if (host == nil or host == "localhost") and defined? UNIXSocket then - unix_socket = socket || ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_ADDR - sock = UNIXSocket::new(unix_socket) - @host_info = Error::err(Error::CR_LOCALHOST_CONNECTION) - @unix_socket = unix_socket - else - sock = TCPSocket::new(host, port||ENV["MYSQL_TCP_PORT"]||(Socket::getservbyname("mysql","tcp") rescue MYSQL_PORT)) - @host_info = sprintf Error::err(Error::CR_TCP_CONNECTION), host - end - @host = host ? host.dup : nil - sock.setsockopt Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true - @net = Net::new sock - - a = read - @protocol_version = a.slice!(0) - @server_version, a = a.split(/\0/,2) - @thread_id, @scramble_buff = a.slice!(0,13).unpack("La8") - if a.size >= 2 then - @server_capabilities, = a.slice!(0,2).unpack("v") - end - if a.size >= 16 then - @server_language, @server_status = a.slice!(0,3).unpack("cv") - end - - flag = 0 if flag == nil - flag |= @client_flag | CLIENT_CAPABILITIES - flag |= CLIENT_CONNECT_WITH_DB if db - - @pre_411 = (0 == @server_capabilities & PROTO_AUTH41) - if @pre_411 - data = Net::int2str(flag)+Net::int3str(@max_allowed_packet)+ - (user||"")+"\0"+ - scramble(passwd, @scramble_buff, @protocol_version==9) - else - dummy, @salt2 = a.unpack("a13a12") - @scramble_buff += @salt2 - flag |= PROTO_AUTH41 - data = Net::int4str(flag) + Net::int4str(@max_allowed_packet) + - ([8] + Array.new(23, 0)).pack("c24") + (user||"")+"\0"+ - scramble41(passwd, @scramble_buff) - end - - if db and @server_capabilities & CLIENT_CONNECT_WITH_DB != 0 - data << "\0" if @pre_411 - data << db - @db = db.dup - end - write data - pkt = read - handle_auth_fallback(pkt, passwd) - ObjectSpace.define_finalizer(self, Mysql.finalizer(@net)) - self - end - alias :connect :real_connect - - def handle_auth_fallback(pkt, passwd) - # A packet like this means that we need to send an old-format password - if pkt.size == 1 and pkt[0] == 254 and - @server_capabilities & CLIENT_SECURE_CONNECTION != 0 then - data = scramble(passwd, @scramble_buff, @protocol_version == 9) - write data + "\0" - read - end - end - - def escape_string(str) - Mysql::escape_string str - end - alias :quote :escape_string - - def get_client_info() - VERSION - end - alias :client_info :get_client_info - - def options(option, arg=nil) - if option == OPT_LOCAL_INFILE then - if arg == false or arg == 0 then - @client_flag &= ~CLIENT_LOCAL_FILES - else - @client_flag |= CLIENT_LOCAL_FILES - end - else - raise "not implemented" - end - end - - def real_query(query) - command COM_QUERY, query, true - read_query_result - self - end - - def use_result() - if @status != :STATUS_GET_RESULT then - error Error::CR_COMMANDS_OUT_OF_SYNC - end - res = Result::new self, @fields, @field_count - @status = :STATUS_USE_RESULT - res - end - - def store_result() - if @status != :STATUS_GET_RESULT then - error Error::CR_COMMANDS_OUT_OF_SYNC - end - @status = :STATUS_READY - data = read_rows @field_count - res = Result::new self, @fields, @field_count, data - @fields = nil - @affected_rows = data.length - res - end - - def change_user(user="", passwd="", db="") - if @pre_411 - data = user+"\0"+scramble(passwd, @scramble_buff, @protocol_version==9)+"\0"+db - else - data = user+"\0"+scramble41(passwd, @scramble_buff)+db - end - pkt = command COM_CHANGE_USER, data - handle_auth_fallback(pkt, passwd) - @user = user - @passwd = passwd - @db = db - end - - def character_set_name() - raise "not implemented" - end - - def close() - @status = :STATUS_READY - command COM_QUIT, nil, true - @net.close - self - end - - def create_db(db) - command COM_CREATE_DB, db - self - end - - def drop_db(db) - command COM_DROP_DB, db - self - end - - def dump_debug_info() - command COM_DEBUG - self - end - - def get_host_info() - @host_info - end - alias :host_info :get_host_info - - def get_proto_info() - @protocol_version - end - alias :proto_info :get_proto_info - - def get_server_info() - @server_version - end - alias :server_info :get_server_info - - def kill(id) - command COM_PROCESS_KILL, Net::int4str(id) - self - end - - def list_dbs(db=nil) - real_query "show databases #{db}" - @status = :STATUS_READY - read_rows(1).flatten - end - - def list_fields(table, field=nil) - command COM_FIELD_LIST, "#{table}\0#{field}", true - if @pre_411 - f = read_rows 6 - else - f = read_rows 7 - end - fields = unpack_fields(f, @server_capabilities & CLIENT_LONG_FLAG != 0) - res = Result::new self, fields, f.length - res.eof = true - res - end - - def list_processes() - data = command COM_PROCESS_INFO - @field_count = get_length data - if @pre_411 - fields = read_rows 5 - else - fields = read_rows 7 - end - @fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0) - @status = :STATUS_GET_RESULT - store_result - end - - def list_tables(table=nil) - real_query "show tables #{table}" - @status = :STATUS_READY - read_rows(1).flatten - end - - def ping() - command COM_PING - self - end - - def query(query) - real_query query - if not @query_with_result then - return self - end - if @field_count == 0 then - return nil - end - store_result - end - - def refresh(r) - command COM_REFRESH, r.chr - self - end - - def reload() - refresh REFRESH_GRANT - self - end - - def select_db(db) - command COM_INIT_DB, db - @db = db - self - end - - def shutdown() - command COM_SHUTDOWN - self - end - - def stat() - command COM_STATISTICS - end - - attr_reader :info, :insert_id, :affected_rows, :field_count, :thread_id - attr_accessor :query_with_result, :status - - def read_one_row(field_count) - data = read - if data[0] == 254 and data.length == 1 ## EOF - return - elsif data[0] == 254 and data.length == 5 - return - end - rec = [] - field_count.times do - len = get_length data - if len == nil then - rec << len - else - rec << data.slice!(0,len) - end - end - rec - end - - def skip_result() - if @status == :STATUS_USE_RESULT then - loop do - data = read - break if data[0] == 254 and data.length == 1 - end - @status = :STATUS_READY - end - end - - def inspect() - "#<#{self.class}>" - end - - private - - def read_query_result() - data = read - @field_count = get_length(data) - if @field_count == nil then # LOAD DATA LOCAL INFILE - File::open(data) do |f| - write f.read - end - write "" # mark EOF - data = read - @field_count = get_length(data) - end - if @field_count == 0 then - @affected_rows = get_length(data, true) - @insert_id = get_length(data, true) - if @server_capabilities & CLIENT_TRANSACTIONS != 0 then - a = data.slice!(0,2) - @server_status = a[0]+a[1]*256 - end - if data.size > 0 and get_length(data) then - @info = data - end - else - @extra_info = get_length(data, true) - if @pre_411 - fields = read_rows(5) - else - fields = read_rows(7) - end - @fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0) - @status = :STATUS_GET_RESULT - end - self - end - - def unpack_fields(data, long_flag_protocol) - ret = [] - data.each do |f| - if @pre_411 - table = org_table = f[0] - name = f[1] - length = f[2][0]+f[2][1]*256+f[2][2]*256*256 - type = f[3][0] - if long_flag_protocol then - flags = f[4][0]+f[4][1]*256 - decimals = f[4][2] - else - flags = f[4][0] - decimals = f[4][1] - end - def_value = f[5] - max_length = 0 - else - catalog = f[0] - db = f[1] - table = f[2] - org_table = f[3] - name = f[4] - org_name = f[5] - length = f[6][2]+f[6][3]*256+f[6][4]*256*256 - type = f[6][6] - flags = f[6][7]+f[6][8]*256 - decimals = f[6][9] - def_value = "" - max_length = 0 - end - ret << Field::new(table, org_table, name, length, type, flags, decimals, def_value, max_length) - end - ret - end - - def read_rows(field_count) - ret = [] - while rec = read_one_row(field_count) do - ret << rec - end - ret - end - - def get_length(data, longlong=nil) - return if data.length == 0 - c = data.slice!(0) - case c - when 251 - return nil - when 252 - a = data.slice!(0,2) - return a[0]+a[1]*256 - when 253 - a = data.slice!(0,3) - return a[0]+a[1]*256+a[2]*256**2 - when 254 - a = data.slice!(0,8) - if longlong then - return a[0]+a[1]*256+a[2]*256**2+a[3]*256**3+ - a[4]*256**4+a[5]*256**5+a[6]*256**6+a[7]*256**7 - else - return a[0]+a[1]*256+a[2]*256**2+a[3]*256**3 - end - else - c - end - end - - def command(cmd, arg=nil, skip_check=nil) - unless @net then - error Error::CR_SERVER_GONE_ERROR - end - if @status != :STATUS_READY then - error Error::CR_COMMANDS_OUT_OF_SYNC - end - @net.clear - write cmd.chr+(arg||"") - read unless skip_check - end - - def read() - unless @net then - error Error::CR_SERVER_GONE_ERROR - end - a = @net.read - if a[0] == 255 then - if a.length > 3 then - @errno = a[1]+a[2]*256 - @error = a[3 .. -1] - else - @errno = Error::CR_UNKNOWN_ERROR - @error = Error::err @errno - end - raise Error::new(@errno, @error) - end - a - end - - def write(arg) - unless @net then - error Error::CR_SERVER_GONE_ERROR - end - @net.write arg - end - - def hash_password(password) - nr = 1345345333 - add = 7 - nr2 = 0x12345671 - password.each_byte do |i| - next if i == 0x20 or i == 9 - nr ^= (((nr & 63) + add) * i) + (nr << 8) - nr2 += (nr2 << 8) ^ nr - add += i - end - [nr & ((1 << 31) - 1), nr2 & ((1 << 31) - 1)] - end - - def scramble(password, message, old_ver) - return "" if password == nil or password == "" - raise "old version password is not implemented" if old_ver - hash_pass = hash_password password - hash_message = hash_password message.slice(0,SCRAMBLE_LENGTH_323) - rnd = Random::new hash_pass[0] ^ hash_message[0], hash_pass[1] ^ hash_message[1] - to = [] - 1.upto(SCRAMBLE_LENGTH_323) do - to << ((rnd.rnd*31)+64).floor - end - extra = (rnd.rnd*31).floor - to.map! do |t| (t ^ extra).chr end - to.join - end - - def scramble41(password, message) - return 0x00.chr if password.nil? or password.empty? - buf = [0x14] - s1 = Digest::SHA1.digest(password) - s2 = Digest::SHA1.digest(s1) - x = Digest::SHA1.digest(message + s2) - (0..s1.length - 1).each {|i| buf.push(s1[i] ^ x[i])} - buf.pack("C*") - end - - def error(errno) - @errno = errno - @error = Error::err errno - raise Error::new(@errno, @error) - end - - class Result - def initialize(mysql, fields, field_count, data=nil) - @handle = mysql - @fields = fields - @field_count = field_count - @data = data - @current_field = 0 - @current_row = 0 - @eof = false - @row_count = 0 - end - attr_accessor :eof - - def data_seek(n) - @current_row = n - end - - def fetch_field() - return if @current_field >= @field_count - f = @fields[@current_field] - @current_field += 1 - f - end - - def fetch_fields() - @fields - end - - def fetch_field_direct(n) - @fields[n] - end - - def fetch_lengths() - @data ? @data[@current_row].map{|i| i ? i.length : 0} : @lengths - end - - def fetch_row() - if @data then - if @current_row >= @data.length then - @handle.status = :STATUS_READY - return - end - ret = @data[@current_row] - @current_row += 1 - else - return if @eof - ret = @handle.read_one_row @field_count - if ret == nil then - @eof = true - return - end - @lengths = ret.map{|i| i ? i.length : 0} - @row_count += 1 - end - ret - end - - def fetch_hash(with_table=nil) - row = fetch_row - return if row == nil - hash = {} - @fields.each_index do |i| - f = with_table ? @fields[i].table+"."+@fields[i].name : @fields[i].name - hash[f] = row[i] - end - hash - end - - def field_seek(n) - @current_field = n - end - - def field_tell() - @current_field - end - - def free() - @handle.skip_result - @handle = @fields = @data = nil - end - - def num_fields() - @field_count - end - - def num_rows() - @data ? @data.length : @row_count - end - - def row_seek(n) - @current_row = n - end - - def row_tell() - @current_row - end - - def each() - while row = fetch_row do - yield row - end - end - - def each_hash(with_table=nil) - while hash = fetch_hash(with_table) do - yield hash - end - end - - def inspect() - "#<#{self.class}>" - end - - end - - class Field - # Field type - TYPE_DECIMAL = 0 - TYPE_TINY = 1 - TYPE_SHORT = 2 - TYPE_LONG = 3 - TYPE_FLOAT = 4 - TYPE_DOUBLE = 5 - TYPE_NULL = 6 - TYPE_TIMESTAMP = 7 - TYPE_LONGLONG = 8 - TYPE_INT24 = 9 - TYPE_DATE = 10 - TYPE_TIME = 11 - TYPE_DATETIME = 12 - TYPE_YEAR = 13 - TYPE_NEWDATE = 14 - TYPE_ENUM = 247 - TYPE_SET = 248 - TYPE_TINY_BLOB = 249 - TYPE_MEDIUM_BLOB = 250 - TYPE_LONG_BLOB = 251 - TYPE_BLOB = 252 - TYPE_VAR_STRING = 253 - TYPE_STRING = 254 - TYPE_GEOMETRY = 255 - TYPE_CHAR = TYPE_TINY - TYPE_INTERVAL = TYPE_ENUM - - # Flag - NOT_NULL_FLAG = 1 - PRI_KEY_FLAG = 2 - UNIQUE_KEY_FLAG = 4 - MULTIPLE_KEY_FLAG = 8 - BLOB_FLAG = 16 - UNSIGNED_FLAG = 32 - ZEROFILL_FLAG = 64 - BINARY_FLAG = 128 - ENUM_FLAG = 256 - AUTO_INCREMENT_FLAG = 512 - TIMESTAMP_FLAG = 1024 - SET_FLAG = 2048 - NUM_FLAG = 32768 - PART_KEY_FLAG = 16384 - GROUP_FLAG = 32768 - UNIQUE_FLAG = 65536 - - def initialize(table, org_table, name, length, type, flags, decimals, def_value, max_length) - @table = table - @org_table = org_table - @name = name - @length = length - @type = type - @flags = flags - @decimals = decimals - @def = def_value - @max_length = max_length - if (type <= TYPE_INT24 and (type != TYPE_TIMESTAMP or length == 14 or length == 8)) or type == TYPE_YEAR then - @flags |= NUM_FLAG - end - end - attr_reader :table, :org_table, :name, :length, :type, :flags, :decimals, :def, :max_length - - def inspect() - "#<#{self.class}:#{@name}>" - end - end - - class Error < StandardError - # Server Error - ER_HASHCHK = 1000 - ER_NISAMCHK = 1001 - ER_NO = 1002 - ER_YES = 1003 - ER_CANT_CREATE_FILE = 1004 - ER_CANT_CREATE_TABLE = 1005 - ER_CANT_CREATE_DB = 1006 - ER_DB_CREATE_EXISTS = 1007 - ER_DB_DROP_EXISTS = 1008 - ER_DB_DROP_DELETE = 1009 - ER_DB_DROP_RMDIR = 1010 - ER_CANT_DELETE_FILE = 1011 - ER_CANT_FIND_SYSTEM_REC = 1012 - ER_CANT_GET_STAT = 1013 - ER_CANT_GET_WD = 1014 - ER_CANT_LOCK = 1015 - ER_CANT_OPEN_FILE = 1016 - ER_FILE_NOT_FOUND = 1017 - ER_CANT_READ_DIR = 1018 - ER_CANT_SET_WD = 1019 - ER_CHECKREAD = 1020 - ER_DISK_FULL = 1021 - ER_DUP_KEY = 1022 - ER_ERROR_ON_CLOSE = 1023 - ER_ERROR_ON_READ = 1024 - ER_ERROR_ON_RENAME = 1025 - ER_ERROR_ON_WRITE = 1026 - ER_FILE_USED = 1027 - ER_FILSORT_ABORT = 1028 - ER_FORM_NOT_FOUND = 1029 - ER_GET_ERRNO = 1030 - ER_ILLEGAL_HA = 1031 - ER_KEY_NOT_FOUND = 1032 - ER_NOT_FORM_FILE = 1033 - ER_NOT_KEYFILE = 1034 - ER_OLD_KEYFILE = 1035 - ER_OPEN_AS_READONLY = 1036 - ER_OUTOFMEMORY = 1037 - ER_OUT_OF_SORTMEMORY = 1038 - ER_UNEXPECTED_EOF = 1039 - ER_CON_COUNT_ERROR = 1040 - ER_OUT_OF_RESOURCES = 1041 - ER_BAD_HOST_ERROR = 1042 - ER_HANDSHAKE_ERROR = 1043 - ER_DBACCESS_DENIED_ERROR = 1044 - ER_ACCESS_DENIED_ERROR = 1045 - ER_NO_DB_ERROR = 1046 - ER_UNKNOWN_COM_ERROR = 1047 - ER_BAD_NULL_ERROR = 1048 - ER_BAD_DB_ERROR = 1049 - ER_TABLE_EXISTS_ERROR = 1050 - ER_BAD_TABLE_ERROR = 1051 - ER_NON_UNIQ_ERROR = 1052 - ER_SERVER_SHUTDOWN = 1053 - ER_BAD_FIELD_ERROR = 1054 - ER_WRONG_FIELD_WITH_GROUP = 1055 - ER_WRONG_GROUP_FIELD = 1056 - ER_WRONG_SUM_SELECT = 1057 - ER_WRONG_VALUE_COUNT = 1058 - ER_TOO_LONG_IDENT = 1059 - ER_DUP_FIELDNAME = 1060 - ER_DUP_KEYNAME = 1061 - ER_DUP_ENTRY = 1062 - ER_WRONG_FIELD_SPEC = 1063 - ER_PARSE_ERROR = 1064 - ER_EMPTY_QUERY = 1065 - ER_NONUNIQ_TABLE = 1066 - ER_INVALID_DEFAULT = 1067 - ER_MULTIPLE_PRI_KEY = 1068 - ER_TOO_MANY_KEYS = 1069 - ER_TOO_MANY_KEY_PARTS = 1070 - ER_TOO_LONG_KEY = 1071 - ER_KEY_COLUMN_DOES_NOT_EXITS = 1072 - ER_BLOB_USED_AS_KEY = 1073 - ER_TOO_BIG_FIELDLENGTH = 1074 - ER_WRONG_AUTO_KEY = 1075 - ER_READY = 1076 - ER_NORMAL_SHUTDOWN = 1077 - ER_GOT_SIGNAL = 1078 - ER_SHUTDOWN_COMPLETE = 1079 - ER_FORCING_CLOSE = 1080 - ER_IPSOCK_ERROR = 1081 - ER_NO_SUCH_INDEX = 1082 - ER_WRONG_FIELD_TERMINATORS = 1083 - ER_BLOBS_AND_NO_TERMINATED = 1084 - ER_TEXTFILE_NOT_READABLE = 1085 - ER_FILE_EXISTS_ERROR = 1086 - ER_LOAD_INFO = 1087 - ER_ALTER_INFO = 1088 - ER_WRONG_SUB_KEY = 1089 - ER_CANT_REMOVE_ALL_FIELDS = 1090 - ER_CANT_DROP_FIELD_OR_KEY = 1091 - ER_INSERT_INFO = 1092 - ER_INSERT_TABLE_USED = 1093 - ER_NO_SUCH_THREAD = 1094 - ER_KILL_DENIED_ERROR = 1095 - ER_NO_TABLES_USED = 1096 - ER_TOO_BIG_SET = 1097 - ER_NO_UNIQUE_LOGFILE = 1098 - ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099 - ER_TABLE_NOT_LOCKED = 1100 - ER_BLOB_CANT_HAVE_DEFAULT = 1101 - ER_WRONG_DB_NAME = 1102 - ER_WRONG_TABLE_NAME = 1103 - ER_TOO_BIG_SELECT = 1104 - ER_UNKNOWN_ERROR = 1105 - ER_UNKNOWN_PROCEDURE = 1106 - ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107 - ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108 - ER_UNKNOWN_TABLE = 1109 - ER_FIELD_SPECIFIED_TWICE = 1110 - ER_INVALID_GROUP_FUNC_USE = 1111 - ER_UNSUPPORTED_EXTENSION = 1112 - ER_TABLE_MUST_HAVE_COLUMNS = 1113 - ER_RECORD_FILE_FULL = 1114 - ER_UNKNOWN_CHARACTER_SET = 1115 - ER_TOO_MANY_TABLES = 1116 - ER_TOO_MANY_FIELDS = 1117 - ER_TOO_BIG_ROWSIZE = 1118 - ER_STACK_OVERRUN = 1119 - ER_WRONG_OUTER_JOIN = 1120 - ER_NULL_COLUMN_IN_INDEX = 1121 - ER_CANT_FIND_UDF = 1122 - ER_CANT_INITIALIZE_UDF = 1123 - ER_UDF_NO_PATHS = 1124 - ER_UDF_EXISTS = 1125 - ER_CANT_OPEN_LIBRARY = 1126 - ER_CANT_FIND_DL_ENTRY = 1127 - ER_FUNCTION_NOT_DEFINED = 1128 - ER_HOST_IS_BLOCKED = 1129 - ER_HOST_NOT_PRIVILEGED = 1130 - ER_PASSWORD_ANONYMOUS_USER = 1131 - ER_PASSWORD_NOT_ALLOWED = 1132 - ER_PASSWORD_NO_MATCH = 1133 - ER_UPDATE_INFO = 1134 - ER_CANT_CREATE_THREAD = 1135 - ER_WRONG_VALUE_COUNT_ON_ROW = 1136 - ER_CANT_REOPEN_TABLE = 1137 - ER_INVALID_USE_OF_NULL = 1138 - ER_REGEXP_ERROR = 1139 - ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140 - ER_NONEXISTING_GRANT = 1141 - ER_TABLEACCESS_DENIED_ERROR = 1142 - ER_COLUMNACCESS_DENIED_ERROR = 1143 - ER_ILLEGAL_GRANT_FOR_TABLE = 1144 - ER_GRANT_WRONG_HOST_OR_USER = 1145 - ER_NO_SUCH_TABLE = 1146 - ER_NONEXISTING_TABLE_GRANT = 1147 - ER_NOT_ALLOWED_COMMAND = 1148 - ER_SYNTAX_ERROR = 1149 - ER_DELAYED_CANT_CHANGE_LOCK = 1150 - ER_TOO_MANY_DELAYED_THREADS = 1151 - ER_ABORTING_CONNECTION = 1152 - ER_NET_PACKET_TOO_LARGE = 1153 - ER_NET_READ_ERROR_FROM_PIPE = 1154 - ER_NET_FCNTL_ERROR = 1155 - ER_NET_PACKETS_OUT_OF_ORDER = 1156 - ER_NET_UNCOMPRESS_ERROR = 1157 - ER_NET_READ_ERROR = 1158 - ER_NET_READ_INTERRUPTED = 1159 - ER_NET_ERROR_ON_WRITE = 1160 - ER_NET_WRITE_INTERRUPTED = 1161 - ER_TOO_LONG_STRING = 1162 - ER_TABLE_CANT_HANDLE_BLOB = 1163 - ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164 - ER_DELAYED_INSERT_TABLE_LOCKED = 1165 - ER_WRONG_COLUMN_NAME = 1166 - ER_WRONG_KEY_COLUMN = 1167 - ER_WRONG_MRG_TABLE = 1168 - ER_DUP_UNIQUE = 1169 - ER_BLOB_KEY_WITHOUT_LENGTH = 1170 - ER_PRIMARY_CANT_HAVE_NULL = 1171 - ER_TOO_MANY_ROWS = 1172 - ER_REQUIRES_PRIMARY_KEY = 1173 - ER_NO_RAID_COMPILED = 1174 - ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175 - ER_KEY_DOES_NOT_EXITS = 1176 - ER_CHECK_NO_SUCH_TABLE = 1177 - ER_CHECK_NOT_IMPLEMENTED = 1178 - ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179 - ER_ERROR_DURING_COMMIT = 1180 - ER_ERROR_DURING_ROLLBACK = 1181 - ER_ERROR_DURING_FLUSH_LOGS = 1182 - ER_ERROR_DURING_CHECKPOINT = 1183 - ER_NEW_ABORTING_CONNECTION = 1184 - ER_DUMP_NOT_IMPLEMENTED = 1185 - ER_FLUSH_MASTER_BINLOG_CLOSED = 1186 - ER_INDEX_REBUILD = 1187 - ER_MASTER = 1188 - ER_MASTER_NET_READ = 1189 - ER_MASTER_NET_WRITE = 1190 - ER_FT_MATCHING_KEY_NOT_FOUND = 1191 - ER_LOCK_OR_ACTIVE_TRANSACTION = 1192 - ER_UNKNOWN_SYSTEM_VARIABLE = 1193 - ER_CRASHED_ON_USAGE = 1194 - ER_CRASHED_ON_REPAIR = 1195 - ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196 - ER_TRANS_CACHE_FULL = 1197 - ER_SLAVE_MUST_STOP = 1198 - ER_SLAVE_NOT_RUNNING = 1199 - ER_BAD_SLAVE = 1200 - ER_MASTER_INFO = 1201 - ER_SLAVE_THREAD = 1202 - ER_TOO_MANY_USER_CONNECTIONS = 1203 - ER_SET_CONSTANTS_ONLY = 1204 - ER_LOCK_WAIT_TIMEOUT = 1205 - ER_LOCK_TABLE_FULL = 1206 - ER_READ_ONLY_TRANSACTION = 1207 - ER_DROP_DB_WITH_READ_LOCK = 1208 - ER_CREATE_DB_WITH_READ_LOCK = 1209 - ER_WRONG_ARGUMENTS = 1210 - ER_NO_PERMISSION_TO_CREATE_USER = 1211 - ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212 - ER_LOCK_DEADLOCK = 1213 - ER_TABLE_CANT_HANDLE_FULLTEXT = 1214 - ER_CANNOT_ADD_FOREIGN = 1215 - ER_NO_REFERENCED_ROW = 1216 - ER_ROW_IS_REFERENCED = 1217 - ER_CONNECT_TO_MASTER = 1218 - ER_QUERY_ON_MASTER = 1219 - ER_ERROR_WHEN_EXECUTING_COMMAND = 1220 - ER_WRONG_USAGE = 1221 - ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222 - ER_CANT_UPDATE_WITH_READLOCK = 1223 - ER_MIXING_NOT_ALLOWED = 1224 - ER_DUP_ARGUMENT = 1225 - ER_USER_LIMIT_REACHED = 1226 - ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227 - ER_LOCAL_VARIABLE = 1228 - ER_GLOBAL_VARIABLE = 1229 - ER_NO_DEFAULT = 1230 - ER_WRONG_VALUE_FOR_VAR = 1231 - ER_WRONG_TYPE_FOR_VAR = 1232 - ER_VAR_CANT_BE_READ = 1233 - ER_CANT_USE_OPTION_HERE = 1234 - ER_NOT_SUPPORTED_YET = 1235 - ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236 - ER_SLAVE_IGNORED_TABLE = 1237 - ER_ERROR_MESSAGES = 238 - - # Client Error - CR_MIN_ERROR = 2000 - CR_MAX_ERROR = 2999 - CR_UNKNOWN_ERROR = 2000 - CR_SOCKET_CREATE_ERROR = 2001 - CR_CONNECTION_ERROR = 2002 - CR_CONN_HOST_ERROR = 2003 - CR_IPSOCK_ERROR = 2004 - CR_UNKNOWN_HOST = 2005 - CR_SERVER_GONE_ERROR = 2006 - CR_VERSION_ERROR = 2007 - CR_OUT_OF_MEMORY = 2008 - CR_WRONG_HOST_INFO = 2009 - CR_LOCALHOST_CONNECTION = 2010 - CR_TCP_CONNECTION = 2011 - CR_SERVER_HANDSHAKE_ERR = 2012 - CR_SERVER_LOST = 2013 - CR_COMMANDS_OUT_OF_SYNC = 2014 - CR_NAMEDPIPE_CONNECTION = 2015 - CR_NAMEDPIPEWAIT_ERROR = 2016 - CR_NAMEDPIPEOPEN_ERROR = 2017 - CR_NAMEDPIPESETSTATE_ERROR = 2018 - CR_CANT_READ_CHARSET = 2019 - CR_NET_PACKET_TOO_LARGE = 2020 - CR_EMBEDDED_CONNECTION = 2021 - CR_PROBE_SLAVE_STATUS = 2022 - CR_PROBE_SLAVE_HOSTS = 2023 - CR_PROBE_SLAVE_CONNECT = 2024 - CR_PROBE_MASTER_CONNECT = 2025 - CR_SSL_CONNECTION_ERROR = 2026 - CR_MALFORMED_PACKET = 2027 - - CLIENT_ERRORS = [ - "Unknown MySQL error", - "Can't create UNIX socket (%d)", - "Can't connect to local MySQL server through socket '%-.64s' (%d)", - "Can't connect to MySQL server on '%-.64s' (%d)", - "Can't create TCP/IP socket (%d)", - "Unknown MySQL Server Host '%-.64s' (%d)", - "MySQL server has gone away", - "Protocol mismatch. Server Version = %d Client Version = %d", - "MySQL client run out of memory", - "Wrong host info", - "Localhost via UNIX socket", - "%-.64s via TCP/IP", - "Error in server handshake", - "Lost connection to MySQL server during query", - "Commands out of sync; You can't run this command now", - "%-.64s via named pipe", - "Can't wait for named pipe to host: %-.64s pipe: %-.32s (%lu)", - "Can't open named pipe to host: %-.64s pipe: %-.32s (%lu)", - "Can't set state of named pipe to host: %-.64s pipe: %-.32s (%lu)", - "Can't initialize character set %-.64s (path: %-.64s)", - "Got packet bigger than 'max_allowed_packet'", - "Embedded server", - "Error on SHOW SLAVE STATUS:", - "Error on SHOW SLAVE HOSTS:", - "Error connecting to slave:", - "Error connecting to master:", - "SSL connection error", - "Malformed packet" - ] - - def initialize(errno, error) - @errno = errno - @error = error - super error - end - attr_reader :errno, :error - - def Error::err(errno) - CLIENT_ERRORS[errno - Error::CR_MIN_ERROR] - end - end - - class Net - def initialize(sock) - @sock = sock - @pkt_nr = 0 - end - - def clear() - @pkt_nr = 0 - end - - def read() - buf = [] - len = nil - @sock.sync = false - while len == nil or len == MAX_PACKET_LENGTH do - a = @sock.read(4) - len = a[0]+a[1]*256+a[2]*256*256 - pkt_nr = a[3] - if @pkt_nr != pkt_nr then - raise "Packets out of order: #{@pkt_nr}<>#{pkt_nr}" - end - @pkt_nr = @pkt_nr + 1 & 0xff - buf << @sock.read(len) - end - @sock.sync = true - buf.join - rescue - errno = Error::CR_SERVER_LOST - raise Error::new(errno, Error::err(errno)) - end - - def write(data) - if data.is_a? Array then - data = data.join - end - @sock.sync = false - ptr = 0 - while data.length >= MAX_PACKET_LENGTH do - @sock.write Net::int3str(MAX_PACKET_LENGTH)+@pkt_nr.chr+data[ptr, MAX_PACKET_LENGTH] - @pkt_nr = @pkt_nr + 1 & 0xff - ptr += MAX_PACKET_LENGTH - end - @sock.write Net::int3str(data.length-ptr)+@pkt_nr.chr+data[ptr .. -1] - @pkt_nr = @pkt_nr + 1 & 0xff - @sock.sync = true - @sock.flush - rescue - errno = Error::CR_SERVER_LOST - raise Error::new(errno, Error::err(errno)) - end - - def close() - @sock.close - end - - def Net::int2str(n) - [n].pack("v") - end - - def Net::int3str(n) - [n%256, n>>8].pack("cv") - end - - def Net::int4str(n) - [n].pack("V") - end - - end - - class Random - def initialize(seed1, seed2) - @max_value = 0x3FFFFFFF - @seed1 = seed1 % @max_value - @seed2 = seed2 % @max_value - end - - def rnd() - @seed1 = (@seed1*3+@seed2) % @max_value - @seed2 = (@seed1+@seed2+33) % @max_value - @seed1.to_f / @max_value - end - end - -end - -class << Mysql - def init() - Mysql::new :INIT - end - - def real_connect(*args) - Mysql::new(*args) - end - alias :connect :real_connect - - def finalizer(net) - proc { - net.clear - begin - net.write(Mysql::COM_QUIT.chr) - net.close - rescue # Ignore IOError if socket is already closed. - end - } - end - - def escape_string(str) - str.gsub(/([\0\n\r\032\'\"\\])/) do - case $1 - when "\0" then "\\0" - when "\n" then "\\n" - when "\r" then "\\r" - when "\032" then "\\Z" - else "\\"+$1 - end - end - end - alias :quote :escape_string - - def get_client_info() - Mysql::VERSION - end - alias :client_info :get_client_info - - def debug(str) - raise "not implemented" - end -end - -# -# for compatibility -# - -MysqlRes = Mysql::Result -MysqlField = Mysql::Field -MysqlError = Mysql::Error |