Stephen
2006-Jul-24 16:15 UTC
[Rails] [PATCH] Support for DB Clusters/Replication in ActiveRecord (RFC)
Skipped content of type multipart/alternative-------------- next part -------------- Index: connection_adapters/abstract/connection_specification.rb ==================================================================--- connection_adapters/abstract/connection_specification.rb (revision 4617) +++ connection_adapters/abstract/connection_specification.rb (working copy) @@ -1,10 +1,21 @@ require ''set'' +require ''pp'' module ActiveRecord class Base class ConnectionSpecification #:nodoc: attr_reader :config, :adapter_method - def initialize (config, adapter_method) + def initialize (config, adapter_method = nil) + # If no adapter is found, try to devine it from the config. + # This was moved here from establish_connection since it''s now + # useful in other places. + if adapter_method.nil? + config = config.symbolize_keys + unless config.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end + adapter_method = "#{config[:adapter]}_connection" + unless ActiveRecord::Base.respond_to?(adapter_method) then + raise AdapterNotFound, "database configuration specifies nonexistent #{config[:adapter]} adapter" end + end @config, @adapter_method = config, adapter_method end end @@ -20,6 +31,16 @@ # The class -> thread id -> adapter cache. (class -> adapter if not allow_concurrency) @@active_connections = {} + # Define a connection pool for doing database reads. + @@read_connection_pool = {} + + # Define a connection pool for doing database reads. + @@write_connection_pool = {} + + # Used in looping the connection pools. + @@last_read_connection = {} + @@last_write_connection = {} + class << self # Retrieve the connection cache. def thread_safe_active_connections #:nodoc: @@ -29,16 +50,32 @@ def single_threaded_active_connections #:nodoc: @@active_connections end - + def thread_safe_read_pool + @@read_connection_pool[Thread.current.object_id] ||= {} + end + def thread_safe_write_pool + @@write_connection_pool[Thread.current.object_id] ||= {} + end + def single_threaded_read_pool + @@read_connection_pool + end + def single_threaded_write_pool + @@write_connection_pool + end + # pick up the right active_connection method from @@allow_concurrency if @@allow_concurrency - alias_method :active_connections, :thread_safe_active_connections + alias_method :active_connections, :thread_safe_active_connections + alias_method :read_connection_pool, :thread_safe_read_pool + alias_method :write_connection_pool, :thread_safe_write_pool else - alias_method :active_connections, :single_threaded_active_connections + alias_method :active_connections, :single_threaded_active_connections + alias_method :read_connection_pool, :single_threaded_read_pool + alias_method :write_connection_pool, :single_threaded_write_pool end # set concurrency support flag (not thread safe, like most of the methods in this file) - def allow_concurrency=(threaded) #:nodoc: + def allow_concurrency=(threaded) #:nodoc logger.debug "allow_concurrency=#{threaded}" if logger return if @@allow_concurrency == threaded clear_all_cached_connections! @@ -52,8 +89,9 @@ end def active_connection_name #:nodoc: + # This must use name, not active_connection_name @active_connection_name ||- if active_connections[name] || @@defined_connections[name] + if write_connection_pool[name] || read_connection_pool[name] name elsif self == ActiveRecord::Base nil @@ -70,13 +108,24 @@ # Returns the connection currently associated with the class. This can # also be used to "borrow" the connection to do database work unrelated # to any of the specific Active Records. - def connection - if @active_connection_name && (conn = active_connections[@active_connection_name]) + def connection(type = nil) + case type + when nil, :write + # Provides write connection on nil for backwards compatibility. + id = round_robin_write_connection.object_id + when :read + id = round_robin_read_connection.object_id + else + # If someone passes garbage, raise error. + # they could have misspelt "read" and that could bork their database. + raise ActiveRecordError, "Connection type can only be read or write." + end + if conn = active_connections[id] conn else # retrieve_connection sets the cache key. - conn = retrieve_connection - active_connections[@active_connection_name] = conn + conn = retrieve_connection(id) + active_connections[id] = conn end end @@ -100,6 +149,28 @@ end end + protected + def round_robin_read_connection + # pp "ActiveConnectionName in round_robin_read is #{active_connection_name}" + # It''s very important to use active_connection_name instead of @active_connection_name + if read_connection_pool[active_connection_name].nil? || read_connection_pool[active_connection_name].size == 0 + raise ConnectionNotEstablished, "No Connection is available" + else + @@last_read_connection[active_connection_name] = (@@last_read_connection[active_connection_name] + 1) % read_connection_pool[active_connection_name].size + read_connection_pool[active_connection_name].to_a[@@last_read_connection[active_connection_name]][1] + end + end + + def round_robin_write_connection + # See comments for round_robin_read_connection. + if write_connection_pool[active_connection_name].nil? || write_connection_pool[active_connection_name].size == 0 + raise ConnectionNotEstablished, "No Connection is available" + else + @@last_write_connection[active_connection_name] = (@@last_write_connection[active_connection_name] + 1) % write_connection_pool[active_connection_name].size + write_connection_pool[active_connection_name].to_a[@@last_write_connection[active_connection_name]][1] + end + end + private def clear_cache!(cache, thread_id = nil, &block) if cache @@ -108,7 +179,6 @@ thread_cache, cache = cache, cache[thread_id] return unless cache end - cache.each(&block) if block_given? cache.clear end @@ -121,11 +191,9 @@ # Remove stale threads from the cache. def remove_stale_cached_threads!(cache, &block) stale = Set.new(cache.keys) - Thread.list.each do |thread| stale.delete(thread.object_id) if thread.alive? end - stale.each do |thread_id| clear_cache!(cache, thread_id, &block) end @@ -147,10 +215,70 @@ # Returns the connection currently associated with the class. This can # also be used to "borrow" the connection to do database work that isn''t # easily done without going straight to SQL. - def connection - self.class.connection + def connection(type = nil) + self.class.connection(type) end + + # Add specs to various connection pools. + def self.append_spec_to_connection_pools spec + # The default behavior allows a connection to be used for both reading and writing. + # Otherwise, we just load both pools. + + write_connection_pool[active_connection_name] ||= {} + read_connection_pool[active_connection_name] ||= {} + @@last_read_connection[active_connection_name] = 0 + @@last_write_connection[active_connection_name] = 0 + # Check out what were were passed if its not a ConnectionSpec, try to make one. + if !spec.kind_of?(ActiveRecord::Base::ConnectionSpecification) + spec = ConnectionSpecification.new(spec) + end + + if !spec.config.key?(:read_only) && !spec.config[:read_only] != ''true'' + write_connection_pool[active_connection_name][spec.object_id] = spec if !write_connection_pool[active_connection_name][spec.object_id] + end + if !spec.config.key?(:write_only) && !spec.config[:write_only] != ''true'' + read_connection_pool[active_connection_name][spec.object_id] = spec if !read_connection_pool[active_connection_name][spec.object_id] + end + @@defined_connections[spec.object_id] = spec + end + + def self.clear_connection_pools(klass=self) + if write_connection_pool[klass.name].kind_of?(Hash) + write_connection_pool[klass.name].each_value {|conn| + active_connections[conn.object_id].disconnect! if active_connections[conn.object_id] } + write_connection_pool[klass.name].clear + end + if read_connection_pool[klass.name].kind_of?(Hash) + read_connection_pool[klass.name].each_value {|conn| + active_connections[conn.object_id].disconnect! if active_connections[conn.object_id] } + read_connection_pool[klass.name].clear + end + read_connection_pool.delete(klass.name) + write_connection_pool.delete(klass.name) + clear_all_cached_connections! + end + + def self.establish_connection_pool(environment) + # Connections that are specifically in a pool override the read/write settings. + @active_connection_name = name + if @read = configurations["#{environment}_read_pool"] + @read.split(",").each do |connection| + spec = configurations[connection].clone + spec[:read_only] = true + append_spec_to_connection_pools(spec) + end + end + if @write = configurations["#{environment}_write_pool"] + @write.split(",").each do |connection| + spec = configurations[connection].clone + spec[:write_only] = true + append_spec_to_connection_pools(spec) + end + end + (write_connection_pool[name].size > 0 && read_connection_pool[name].size > 0) ? true : false + end + # Establishes the connection to the database. Accepts a hash as input where # the :adapter key must be specified with the name of a database adapter (in lower-case) # example for regular databases (MySQL, Postgresql, etc): @@ -182,11 +310,13 @@ case spec when nil raise AdapterNotSpecified unless defined? RAILS_ENV - establish_connection(RAILS_ENV) + if !establish_connection_pool(RAILS_ENV) + establish_connection(RAILS_ENV) + end when ConnectionSpecification - clear_active_connection_name + clear_active_connection_name @active_connection_name = name - @@defined_connections[name] = spec + append_spec_to_connection_pools(spec) when Symbol, String if configuration = configurations[spec.to_s] establish_connection(configuration) @@ -194,12 +324,7 @@ raise AdapterNotSpecified, "#{spec} database is not configured" end else - spec = spec.symbolize_keys - unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end - adapter_method = "#{spec[:adapter]}_connection" - unless respond_to?(adapter_method) then raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" end - remove_connection - establish_connection(ConnectionSpecification.new(spec, adapter_method)) + establish_connection(ConnectionSpecification.new(spec)) end end @@ -207,27 +332,29 @@ # active or defined connections: if it is the latter, it will be # opened and set as the active connection for the class it was defined # for (not necessarily the current class). - def self.retrieve_connection #:nodoc: + def self.retrieve_connection(id = nil) #:nodoc: # Name is nil if establish_connection hasn''t been called for # some class along the inheritance chain up to AR::Base yet. - if name = active_connection_name - if conn = active_connections[name] - # Verify the connection. - conn.verify!(@@verification_timeout) - elsif spec = @@defined_connections[name] - # Activate this connection specification. - klass = name.constantize - klass.connection = spec - conn = active_connections[name] - end + if id.nil? + id = round_robin_write_connection.object_id end - + if conn = active_connections[id] + # Verify the connection. + conn.verify!(@@verification_timeout) + elsif spec = @@defined_connections[id] + # Activate this connection specification. + # Note this no longer calls connection= which causes problems + # with read/write pools being clobbered. + config = spec.config.reverse_merge(:allow_concurrency => @@allow_concurrency) + active_connections[id] = self.send(spec.adapter_method, config) + conn = active_connections[id] + end conn or raise ConnectionNotEstablished end # Returns true if a connection that''s accessible to this class have already been opened. def self.connected? - active_connections[active_connection_name] ? true : false + active_connections[active_connection_name][round_robin_write_connection.object_id] ? true : false end # Remove the connection for this class. This will close the active @@ -235,21 +362,20 @@ # can be used as argument for establish_connection, for easy # re-establishing of the connection. def self.remove_connection(klass=self) - spec = @@defined_connections[klass.name] - konn = active_connections[klass.name] - @@defined_connections.delete_if { |key, value| value == spec } - active_connections.delete_if { |key, value| value == konn } - konn.disconnect! if konn - spec.config if spec + # Arbitarily choosing the first spec off the write pool to send back. + # This is here to stay backwards compatible + spec = @@defined_connections[round_robin_write_connection.object_id] if !@@defined_connections[round_robin_write_connection.object_id].nil? + clear_connection_pools(klass) + spec if spec end # Set the connection for the class. def self.connection=(spec) #:nodoc: if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter) - active_connections[name] = spec + active_connections[active_connection_name] = spec elsif spec.kind_of?(ConnectionSpecification) config = spec.config.reverse_merge(:allow_concurrency => @@allow_concurrency) - self.connection = self.send(spec.adapter_method, config) + establish_connection(spec.adapter_method, config) elsif spec.nil? raise ConnectionNotEstablished else @@ -265,5 +391,5 @@ logger.info "Active connection name: #{@active_connection_name}" end end - end + end_pool end Index: base.rb ==================================================================--- base.rb (revision 4617) +++ base.rb (working copy) @@ -419,7 +419,7 @@ # Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id" # Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date] def find_by_sql(sql) - connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) } + connection(:read).select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) } end # Returns true if the given +id+ represents the primary key of a record in the database, false otherwise. @@ -506,7 +506,7 @@ # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id" def count_by_sql(sql) sql = sanitize_conditions(sql) - connection.select_value(sql, "#{name} Count").to_i + connection(:read).select_value(sql, "#{name} Count").to_i end # Increments the specified counter by one. So <tt>DiscussionBoard.increment_counter("post_count", @@ -725,7 +725,7 @@ # Returns an array of column objects for the table associated with this class. def columns unless @columns - @columns = connection.columns(table_name, "#{name} Columns") + @columns = connection(:read).columns(table_name, "#{name} Columns") @columns.each {|column| column.primary = column.name == primary_key} end @columns
Tom Mornini
2006-Jul-24 17:41 UTC
[Rails] [PATCH] Support for DB Clusters/Replication in ActiveRecord (RFC)
On Jul 24, 2006, at 9:13 AM, Stephen wrote:> Defining a database pool is very easy and follows a common sense > convention. I will assume you are running with > RAILS_ENV=development for this example. You first define your > connections as you always have in database.yml . Your are then > able to able to define > development_read_pool: db1, db2, db3 > development_write_pool: db4, db5, db6 (In general, the name is > RAILS_ENV_write_pool so you can test your clusters in development > and production with no config changes)This is very cool, Stephen. Couldn''t this be read_pool, and write_pool, within the current block of YAML? -- -- Tom Mornini
Stephen
2006-Jul-24 17:48 UTC
[Rails] [PATCH] Support for DB Clusters/Replication in ActiveRecord (RFC)
Here is an example. I considered the merits of allow an attribute like pools: p1,p2,p3 but I think this makes more sense from a configuration standpoint. It would look something like this; development: adapter: mysql database: project_development username: someuser password: happyland host: 10.0.0.1 port: 3306 development2: adapter: mysql database: project_development username: root password: somethingelse host: 10.0.0.2 port: 3306 development_write_pool: development development_read_pool: development,development2 On 7/24/06, Tom Mornini <tmornini@infomania.com> wrote:> > On Jul 24, 2006, at 9:13 AM, Stephen wrote: > > > Defining a database pool is very easy and follows a common sense > > convention. I will assume you are running with > > RAILS_ENV=development for this example. You first define your > > connections as you always have in database.yml . Your are then > > able to able to define > > development_read_pool: db1, db2, db3 > > development_write_pool: db4, db5, db6 (In general, the name is > > RAILS_ENV_write_pool so you can test your clusters in development > > and production with no config changes) > > This is very cool, Stephen. > > Couldn''t this be read_pool, and write_pool, within the current block > of YAML? > > -- > -- Tom Mornini > > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails >-------------- next part -------------- An HTML attachment was scrubbed... URL: http://wrath.rubyonrails.org/pipermail/rails/attachments/20060724/0114edff/attachment.html
Hi, I''ve tried creating a new ticket for this issue, but I''m just getting 500 server errors. There appears to be a problem with the ActiveRecord SqlServer adapter that results in illegal SQL being generated... ActiveRecord::StatementInvalid in ArticlesController#index DBI::DatabaseError: 37000 (170) [Microsoft][ODBC SQL Server Driver][SQL Server]Line 1: Incorrect syntax near ''\''.: SELECT * FROM (SELECT TOP 1 * FROM (SELECT TOP 1 id FROM contents WHERE (published = 1 AND contents.published_at < ''2006-07-25 11:59:31'' AND blog_id = 1) AND ( (contents.[type] = ''Article'' ) ) ORDER BY contents.published_at DESC ) AS tmp1 ORDER BY contents\.\[published_at\] ASC) AS tmp2 ORDER BY contents\.\[published_at\] DESC This is due to messed up column quoting. I''ve tracked it down to the ''add_limit_offset!'' method in sqlserver_adapter.rb, but I can''t decipher enough of what is going on to suggest the fix just yet. Full stack trace also included below. Regards, Stu RAILS_ROOT: ./script/../config/.. Application Trace | Framework Trace | Full Trace #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada pters/abstract_adapter.rb:120:in `log'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada pters/sqlserver_adapter.rb:485:in `select'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada pters/sqlserver_adapter.rb:233:in `select_all'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r b:1166:in `select_limited_ids_list'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r b:1158:in `add_limited_ids_condition!'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r b:1148:in `construct_finder_sql_with_included_associations'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r b:1111:in `select_all_rows'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r b:975:in `find_with_associations'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r b:973:in `find_with_associations'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:923:in `find_every'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:381:in `find'' #{RAILS_ROOT}/app/controllers/articles_controller.rb:27:in `index'' #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:910: in `perform_action_without_filters'' #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3 68:in `perform_action_without_benchmark'' #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking .rb:69:in `perform_action_without_rescue'' c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure'' #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking .rb:69:in `perform_action_without_rescue'' #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/rescue.rb:82 :in `perform_action'' #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:381: in `process_without_filters'' #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3 77:in `process_without_session_management_support'' #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/session_mana gement.rb:117:in `process'' #{RAILS_ROOT}/vendor/rails/railties/lib/dispatcher.rb:38:in `dispatch'' #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:115:in `handle_dispatch'' #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:81:in `service'' c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'' c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'' c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'' c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'' c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start'' c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start'' c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start'' c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start'' #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:67:in `dispatch'' #{RAILS_ROOT}/vendor/rails/railties/lib/commands/servers/webrick.rb:59 c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'' #{RAILS_ROOT}/vendor/rails/activesupport/lib/active_support/dependencies .rb:147:in `require'' #{RAILS_ROOT}/vendor/rails/railties/lib/commands/server.rb:30 c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'' script/server:3 Request Parameters: None Show session dump --- Response Headers: {"cookie"=>[], "Cache-Control"=>"no-cache"} -- Ticket URL: <http://www.typosphere.org/trac/ticket/1040> typo <http://typosphere.org/trac/> web2.0 weblogging engine <body> <blockquote> <font FACE="Arial,Arial" SIZE="1"><p ALIGN="JUSTIFY">This Email may contain confidential and privileged information and is intended for the use of the addressee(s) only. If you are not the intended recipient please notify the sender and delete the Email from your system. It should not be transmitted to any other person without the consent of the sender. Additional important notifications regarding Email transmissions from and to members of Baring Asset Management can be accessed at <a href="http://www.barings.com/email/index.hcst">http://www.barings.com/email/index.hcst</a></font></p></body> </p> </font> </blockquote> <p> </p> </body> </html>
Interesting. I''ll take a look this evening. Please let me know if you find out any more. Tom On 25/07/06, Stuart Holden <Stuart.Holden@baring-asset.com> wrote:> Hi, > > > I''ve tried creating a new ticket for this issue, but I''m just getting > 500 server errors. > > There appears to be a problem with the ActiveRecord SqlServer adapter > that results in illegal SQL being generated... > > > ActiveRecord::StatementInvalid in ArticlesController#index > DBI::DatabaseError: 37000 (170) [Microsoft][ODBC SQL Server Driver][SQL > Server]Line 1: Incorrect syntax near ''\''.: SELECT * FROM (SELECT TOP 1 > * > FROM (SELECT TOP 1 id FROM contents WHERE (published = 1 AND > contents.published_at < ''2006-07-25 11:59:31'' AND blog_id = 1) AND ( > (contents.[type] = ''Article'' ) ) ORDER BY contents.published_at DESC ) > AS > tmp1 ORDER BY contents\.\[published_at\] ASC) AS tmp2 ORDER BY > contents\.\[published_at\] DESC > > This is due to messed up column quoting. I''ve tracked it down to the > ''add_limit_offset!'' method in sqlserver_adapter.rb, but I can''t decipher > enough of what is going on to suggest the fix just yet. > > Full stack trace also included below. > > Regards, > Stu > > RAILS_ROOT: ./script/../config/.. > Application Trace | Framework Trace | Full Trace > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada > pters/abstract_adapter.rb:120:in > `log'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada > pters/sqlserver_adapter.rb:485:in > `select'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada > pters/sqlserver_adapter.rb:233:in > `select_all'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > b:1166:in > `select_limited_ids_list'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > b:1158:in > `add_limited_ids_condition!'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > b:1148:in > `construct_finder_sql_with_included_associations'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > b:1111:in > `select_all_rows'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > b:975:in > `find_with_associations'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > b:973:in > `find_with_associations'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:923:in > `find_every'' > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:381:in > `find'' > #{RAILS_ROOT}/app/controllers/articles_controller.rb:27:in `index'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:910: > in > `perform_action_without_filters'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3 > 68:in > `perform_action_without_benchmark'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking > .rb:69:in > `perform_action_without_rescue'' > c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking > .rb:69:in > `perform_action_without_rescue'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/rescue.rb:82 > :in > `perform_action'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:381: > in > `process_without_filters'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3 > 77:in > `process_without_session_management_support'' > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/session_mana > gement.rb:117:in > `process'' > #{RAILS_ROOT}/vendor/rails/railties/lib/dispatcher.rb:38:in `dispatch'' > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:115:in > `handle_dispatch'' > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:81:in > `service'' > c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'' > c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start'' > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:67:in > `dispatch'' > #{RAILS_ROOT}/vendor/rails/railties/lib/commands/servers/webrick.rb:59 > c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in > `require'' > > #{RAILS_ROOT}/vendor/rails/activesupport/lib/active_support/dependencies > .rb:147:in > `require'' > #{RAILS_ROOT}/vendor/rails/railties/lib/commands/server.rb:30 > c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in > `require'' > script/server:3 > Request > > Parameters: None > > Show session dump > --- > Response > Headers: {"cookie"=>[], "Cache-Control"=>"no-cache"} > > -- > Ticket URL: <http://www.typosphere.org/trac/ticket/1040> > typo <http://typosphere.org/trac/> > web2.0 weblogging engine > > > <body> > > <blockquote> > <font FACE="Arial,Arial" SIZE="1"><p ALIGN="JUSTIFY">This Email may contain confidential and privileged information and is intended for the use of the addressee(s) only. If you are not the intended recipient please notify the sender and delete the Email from your system. It should not be transmitted to any other person without the consent of the sender. Additional important notifications regarding Email transmissions from and to members of Baring Asset Management can be accessed at <a href="http://www.barings.com/email/index.hcst">http://www.barings.com/email/index.hcst</a></font></p></body> > </p> > </font> > </blockquote> > > <p> </p> > </body> > </html> > > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails >
I think I''ve managed to fix the column quoting issue by making three small changes to the add_limit_offset! method in the sqlserver_adapter.rb file: def add_limit_offset!(sql, options) if options[:limit] and options[:offset] total_rows = @connection.select_all("SELECT count(*) as TotalRows from (#{sql.gsub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP 1000000000")}) tally")[0][:TotalRows].to_i if (options[:limit] + options[:offset]) >= total_rows options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0 end sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]} ") sql << ") AS tmp1" if options[:order] options[:order] = options[:order].split('','').map do |field| parts = field.split(" ") tc = parts[0] if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query ####-- ORIGINAL : tc.gsub!(/\./, ''\\.\\['') ####-- ORIGINAL : tc << ''\\]'' tc.gsub!(/\./, ''.['') #NEW: -- do not escape column quotes tc << '']'' #NEW: -- do not escape column quotes end if sql =~ /#{tc} AS (t\d_r\d\d?)/ parts[0] = $1 ####-- ORIGINAL : elsif parts[0] =~ /\w+\.(\w+)/ elsif parts[0] =~ /\w+\.([\[\]\w]+)/ #NEW: ensure that column quotes are picked up when matching to strip the tablename parts[0] = $1 end parts.join('' '') end.join('', '') sql << " ORDER BY #{change_order_direction(options[:order])}) AS tmp2 ORDER BY #{options[:order]}" else sql << " ) AS tmp2" end elsif sql !~ /^\s*SELECT (@@|COUNT\()/i sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do "SELECT#{$1} TOP #{options[:limit]}" end unless options[:limit].nil? end end This has fixed the column quoting issue, but there are still some problems with the SQL that ActiveRecord is generating. The original issue of selecting by offset now works... Article.find( :all, :offset => 0, :limit => 1, :order => "contents.published_at DESC", :conditions => [''published = ? AND contents.published_at < ? AND blog_id = ?'',true, Time.now, 1]) [#<Article:0x32acb34 @attributes={... (snip) And selecting with the :include tag works... Article.find( :all, :order => "contents.published_at DESC", :include => [:categories, :tags, :user, :blog], :conditions => [''published = ? AND contents.published_at < ? AND blog_id = ?'',true, Time.now, 1]) [#<Article:0x3848110 @user=#<User:0x38476fc @attributes={... (snip) But a find that combines both the :offset and :include will fail due to bad SQL... Article.find( :all, :offset => 0, :limit => 1, :order => "contents.published_at DESC", :include => [:categories, :tags, :user, :blog], :conditions => [''published = ? AND contents.published_at < ? AND blog_id = ?'',true, Time.now, 1]) ActiveRecord::StatementInvalid: DBI::DatabaseError: S0022 (207) [Microsoft][ODBC SQL Server Driver][SQL Server]Invalid column name ''published_at''.: SELECT * FROM (SELECT TOP 1 * FROM (SELECT TOP 1 id FROM contents WHERE (published = 1 AND contents.published_at < ''2006-07-26 12:24:01'' AND blog_id = 1) AND ( (contents.[type] ''Article'' ) ) ORDER BY contents.published_at DESC ) AS tmp1 ORDER BY [published_at] ASC) AS tmp2 ORDER BY [published_at] DESC from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/connection_adapters/abstract_adapter.rb:120:in `log'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/connection_adapters/sqlserver_adapter.rb:521:in `select'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/connection_adapters/sqlserver_adapter.rb:257:in `select_all'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/associations.rb:1166:in `select_limited_ids_list'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/associations.rb:1158:in `add_limited_ids_condition!'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/associations.rb:1148:in `construct_finder_sql_with_included_associations'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/associations.rb:1111:in `select_all_rows'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/associations.rb:975:in `find_with_associations'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/associations.rb:973:in `find_with_associations'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/base.rb:923:in `find_every'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/base.rb:381:in `find'' from (irb):104 from :0 This one is caused by just selecting the [id] in the inner select, and not exposing the the [published_at] column to be sorted on in the outer selects. Sadly I haven''t been able to get anywhere with debugging this one. Also, please note that this is being run against the Typo 4 database, and the test are run via the script/console util. The issues do also occur through the web frontend. Regards, Stu -----Original Message----- From: rails-bounces@lists.rubyonrails.org [mailto:rails-bounces@lists.rubyonrails.org] On Behalf Of Tom Ward Sent: 25 July 2006 16:18 To: rails@lists.rubyonrails.org Subject: Re: [Rails] ActiveRecord SQL Server Adapter Bug... Interesting. I''ll take a look this evening. Please let me know if you find out any more. Tom On 25/07/06, Stuart Holden <Stuart.Holden@baring-asset.com> wrote:> Hi, > > > I''ve tried creating a new ticket for this issue, but I''m just getting > 500 server errors. > > There appears to be a problem with the ActiveRecord SqlServer adapter > that results in illegal SQL being generated... > > > ActiveRecord::StatementInvalid in ArticlesController#index > DBI::DatabaseError: 37000 (170) [Microsoft][ODBC SQL ServerDriver][SQL> Server]Line 1: Incorrect syntax near ''\''.: SELECT * FROM (SELECT TOP1> * > FROM (SELECT TOP 1 id FROM contents WHERE (published = 1 AND > contents.published_at < ''2006-07-25 11:59:31'' AND blog_id = 1) AND ( > (contents.[type] = ''Article'' ) ) ORDER BY contents.published_at DESC)> AS > tmp1 ORDER BY contents\.\[published_at\] ASC) AS tmp2 ORDER BY > contents\.\[published_at\] DESC > > This is due to messed up column quoting. I''ve tracked it down to the > ''add_limit_offset!'' method in sqlserver_adapter.rb, but I can''tdecipher> enough of what is going on to suggest the fix just yet. > > Full stack trace also included below. > > Regards, > Stu > > RAILS_ROOT: ./script/../config/.. > Application Trace | Framework Trace | Full Trace > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada> pters/abstract_adapter.rb:120:in > `log'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada> pters/sqlserver_adapter.rb:485:in > `select'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada> pters/sqlserver_adapter.rb:233:in > `select_all'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1166:in > `select_limited_ids_list'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1158:in > `add_limited_ids_condition!'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1148:in > `construct_finder_sql_with_included_associations'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1111:in > `select_all_rows'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:975:in > `find_with_associations'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:973:in > `find_with_associations'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:923:in> `find_every'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:381:in> `find'' > #{RAILS_ROOT}/app/controllers/articles_controller.rb:27:in `index'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:910:> in > `perform_action_without_filters'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3> 68:in > `perform_action_without_benchmark'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking> .rb:69:in > `perform_action_without_rescue'' > c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking> .rb:69:in > `perform_action_without_rescue'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/rescue.rb:82> :in > `perform_action'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:381:> in > `process_without_filters'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3> 77:in > `process_without_session_management_support'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/session_mana> gement.rb:117:in > `process'' > #{RAILS_ROOT}/vendor/rails/railties/lib/dispatcher.rb:38:in`dispatch''> #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:115:in > `handle_dispatch'' > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:81:in > `service'' > c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'' > c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start'' > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:67:in > `dispatch'' >#{RAILS_ROOT}/vendor/rails/railties/lib/commands/servers/webrick.rb:59> c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in > `require'' > >#{RAILS_ROOT}/vendor/rails/activesupport/lib/active_support/dependencies> .rb:147:in > `require'' > #{RAILS_ROOT}/vendor/rails/railties/lib/commands/server.rb:30 > c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in > `require'' > script/server:3 > Request > > Parameters: None > > Show session dump > --- > Response > Headers: {"cookie"=>[], "Cache-Control"=>"no-cache"} > > -- > Ticket URL: <http://www.typosphere.org/trac/ticket/1040> > typo <http://typosphere.org/trac/> > web2.0 weblogging engine > > > <body> > > <blockquote> > <font FACE="Arial,Arial" SIZE="1"><p ALIGN="JUSTIFY">This Email maycontain confidential and privileged information and is intended for the use of the addressee(s) only. If you are not the intended recipient please notify the sender and delete the Email from your system. It should not be transmitted to any other person without the consent of the sender. Additional important notifications regarding Email transmissions from and to members of Baring Asset Management can be accessed at <a href="http://www.barings.com/email/index.hcst">http://www.barings.com/em ail/index.hcst</a></font></p></body>> </p> > </font> > </blockquote> > > <p> </p> > </body> > </html> > > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails >_______________________________________________ Rails mailing list Rails@lists.rubyonrails.org http://lists.rubyonrails.org/mailman/listinfo/rails <body> <blockquote> <font FACE="Arial,Arial" SIZE="1"><p ALIGN="JUSTIFY">This Email may contain confidential and privileged information and is intended for the use of the addressee(s) only. If you are not the intended recipient please notify the sender and delete the Email from your system. It should not be transmitted to any other person without the consent of the sender. Additional important notifications regarding Email transmissions from and to members of Baring Asset Management can be accessed at <a href="http://www.barings.com/email/index.hcst">http://www.barings.com/email/index.hcst</a></font></p></body> </p> </font> </blockquote> <p> </p> </body> </html>
-----Original Message----- From: Stuart Holden Sent: 26 July 2006 12:38 To: ''rails@lists.rubyonrails.org'' Subject: RE: [Rails] ActiveRecord SQL Server Adapter Bug... I think I''ve managed to fix the column quoting issue by making three small changes to the add_limit_offset! method in the sqlserver_adapter.rb file: def add_limit_offset!(sql, options) if options[:limit] and options[:offset] total_rows = @connection.select_all("SELECT count(*) as TotalRows from (#{sql.gsub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP 1000000000")}) tally")[0][:TotalRows].to_i if (options[:limit] + options[:offset]) >= total_rows options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0 end sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]} ") sql << ") AS tmp1" if options[:order] options[:order] = options[:order].split('','').map do |field| parts = field.split(" ") tc = parts[0] if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query ####-- ORIGINAL : tc.gsub!(/\./, ''\\.\\['') ####-- ORIGINAL : tc << ''\\]'' tc.gsub!(/\./, ''.['') #NEW: -- do not escape column quotes tc << '']'' #NEW: -- do not escape column quotes end if sql =~ /#{tc} AS (t\d_r\d\d?)/ parts[0] = $1 ####-- ORIGINAL : elsif parts[0] =~ /\w+\.(\w+)/ elsif parts[0] =~ /\w+\.([\[\]\w]+)/ #NEW: ensure that column quotes are picked up when matching to strip the tablename parts[0] = $1 end parts.join('' '') end.join('', '') sql << " ORDER BY #{change_order_direction(options[:order])}) AS tmp2 ORDER BY #{options[:order]}" else sql << " ) AS tmp2" end elsif sql !~ /^\s*SELECT (@@|COUNT\()/i sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do "SELECT#{$1} TOP #{options[:limit]}" end unless options[:limit].nil? end end This has fixed the column quoting issue, but there are still some problems with the SQL that ActiveRecord is generating. The original issue of selecting by offset now works... Article.find( :all, :offset => 0, :limit => 1, :order => "contents.published_at DESC", :conditions => [''published = ? AND contents.published_at < ? AND blog_id = ?'',true, Time.now, 1]) [#<Article:0x32acb34 @attributes={... (snip) And selecting with the :include tag works... Article.find( :all, :order => "contents.published_at DESC", :include => [:categories, :tags, :user, :blog], :conditions => [''published = ? AND contents.published_at < ? AND blog_id = ?'',true, Time.now, 1]) [#<Article:0x3848110 @user=#<User:0x38476fc @attributes={... (snip) But a find that combines both the :offset and :include will fail due to bad SQL... Article.find( :all, :offset => 0, :limit => 1, :order => "contents.published_at DESC", :include => [:categories, :tags, :user, :blog], :conditions => [''published = ? AND contents.published_at < ? AND blog_id = ?'',true, Time.now, 1]) ActiveRecord::StatementInvalid: DBI::DatabaseError: S0022 (207) [Microsoft][ODBC SQL Server Driver][SQL Server]Invalid column name ''published_at''.: SELECT * FROM (SELECT TOP 1 * FROM (SELECT TOP 1 id FROM contents WHERE (published = 1 AND contents.published_at < ''2006-07-26 12:24:01'' AND blog_id = 1) AND ( (contents.[type] ''Article'' ) ) ORDER BY contents.published_at DESC ) AS tmp1 ORDER BY [published_at] ASC) AS tmp2 ORDER BY [published_at] DESC from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/connection_adapters/abstract_adapter.rb:120:in `log'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/connection_adapters/sqlserver_adapter.rb:521:in `select'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/connection_adapters/sqlserver_adapter.rb:257:in `select_all'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/associations.rb:1166:in `select_limited_ids_list'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/associations.rb:1158:in `add_limited_ids_condition!'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/associations.rb:1148:in `construct_finder_sql_with_included_associations'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/associations.rb:1111:in `select_all_rows'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/associations.rb:975:in `find_with_associations'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/associations.rb:973:in `find_with_associations'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/base.rb:923:in `find_every'' from ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec ord/base.rb:381:in `find'' from (irb):104 from :0 This one is caused by just selecting the [id] in the inner select, and not exposing the the [published_at] column to be sorted on in the outer selects. Sadly I haven''t been able to get anywhere with debugging this one. Also, please note that this is being run against the Typo 4 database, and the test are run via the script/console util. The issues do also occur through the web frontend. Regards, Stu -----Original Message----- From: rails-bounces@lists.rubyonrails.org [mailto:rails-bounces@lists.rubyonrails.org] On Behalf Of Tom Ward Sent: 25 July 2006 16:18 To: rails@lists.rubyonrails.org Subject: Re: [Rails] ActiveRecord SQL Server Adapter Bug... Interesting. I''ll take a look this evening. Please let me know if you find out any more. Tom On 25/07/06, Stuart Holden <Stuart.Holden@baring-asset.com> wrote:> Hi, > > > I''ve tried creating a new ticket for this issue, but I''m just getting > 500 server errors. > > There appears to be a problem with the ActiveRecord SqlServer adapter > that results in illegal SQL being generated... > > > ActiveRecord::StatementInvalid in ArticlesController#index > DBI::DatabaseError: 37000 (170) [Microsoft][ODBC SQL ServerDriver][SQL> Server]Line 1: Incorrect syntax near ''\''.: SELECT * FROM (SELECT TOP1> * > FROM (SELECT TOP 1 id FROM contents WHERE (published = 1 AND > contents.published_at < ''2006-07-25 11:59:31'' AND blog_id = 1) AND ( > (contents.[type] = ''Article'' ) ) ORDER BY contents.published_at DESC)> AS > tmp1 ORDER BY contents\.\[published_at\] ASC) AS tmp2 ORDER BY > contents\.\[published_at\] DESC > > This is due to messed up column quoting. I''ve tracked it down to the > ''add_limit_offset!'' method in sqlserver_adapter.rb, but I can''tdecipher> enough of what is going on to suggest the fix just yet. > > Full stack trace also included below. > > Regards, > Stu > > RAILS_ROOT: ./script/../config/.. > Application Trace | Framework Trace | Full Trace > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada> pters/abstract_adapter.rb:120:in > `log'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada> pters/sqlserver_adapter.rb:485:in > `select'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada> pters/sqlserver_adapter.rb:233:in > `select_all'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1166:in > `select_limited_ids_list'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1158:in > `add_limited_ids_condition!'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1148:in > `construct_finder_sql_with_included_associations'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:1111:in > `select_all_rows'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:975:in > `find_with_associations'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r> b:973:in > `find_with_associations'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:923:in> `find_every'' > >#{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:381:in> `find'' > #{RAILS_ROOT}/app/controllers/articles_controller.rb:27:in `index'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:910:> in > `perform_action_without_filters'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3> 68:in > `perform_action_without_benchmark'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking> .rb:69:in > `perform_action_without_rescue'' > c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking> .rb:69:in > `perform_action_without_rescue'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/rescue.rb:82> :in > `perform_action'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:381:> in > `process_without_filters'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3> 77:in > `process_without_session_management_support'' > >#{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/session_mana> gement.rb:117:in > `process'' > #{RAILS_ROOT}/vendor/rails/railties/lib/dispatcher.rb:38:in`dispatch''> #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:115:in > `handle_dispatch'' > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:81:in > `service'' > c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'' > c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start'' > c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start'' > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:67:in > `dispatch'' >#{RAILS_ROOT}/vendor/rails/railties/lib/commands/servers/webrick.rb:59> c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in > `require'' > >#{RAILS_ROOT}/vendor/rails/activesupport/lib/active_support/dependencies> .rb:147:in > `require'' > #{RAILS_ROOT}/vendor/rails/railties/lib/commands/server.rb:30 > c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in > `require'' > script/server:3 > Request > > Parameters: None > > Show session dump > --- > Response > Headers: {"cookie"=>[], "Cache-Control"=>"no-cache"} > > -- > Ticket URL: <http://www.typosphere.org/trac/ticket/1040> > typo <http://typosphere.org/trac/> > web2.0 weblogging engine > > > <body> > > <blockquote> > <font FACE="Arial,Arial" SIZE="1"><p ALIGN="JUSTIFY">This Email maycontain confidential and privileged information and is intended for the use of the addressee(s) only. If you are not the intended recipient please notify the sender and delete the Email from your system. It should not be transmitted to any other person without the consent of the sender. Additional important notifications regarding Email transmissions from and to members of Baring Asset Management can be accessed at <a href="http://www.barings.com/email/index.hcst">http://www.barings.com/em ail/index.hcst</a></font></p></body>> </p> > </font> > </blockquote> > > <p> </p> > </body> > </html> > > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails >_______________________________________________ Rails mailing list Rails@lists.rubyonrails.org http://lists.rubyonrails.org/mailman/listinfo/rails <body> <blockquote> <font FACE="Arial,Arial" SIZE="1"><p ALIGN="JUSTIFY">This Email may contain confidential and privileged information and is intended for the use of the addressee(s) only. If you are not the intended recipient please notify the sender and delete the Email from your system. It should not be transmitted to any other person without the consent of the sender. Additional important notifications regarding Email transmissions from and to members of Baring Asset Management can be accessed at <a href="http://www.barings.com/email/index.hcst">http://www.barings.com/email/index.hcst</a></font></p></body> </p> </font> </blockquote> <p> </p> </body> </html>
Thanks for the extra info. I hate that limit/offset thing, cause of all my sorrow. Tom On 26/07/06, Stuart Holden <Stuart.Holden@baring-asset.com> wrote:> I think I''ve managed to fix the column quoting issue by making three > small changes to the add_limit_offset! method in the > sqlserver_adapter.rb file: > > def add_limit_offset!(sql, options) > > if options[:limit] and options[:offset] > total_rows = @connection.select_all("SELECT count(*) as > TotalRows from (#{sql.gsub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP > 1000000000")}) tally")[0][:TotalRows].to_i > if (options[:limit] + options[:offset]) >= total_rows > options[:limit] = (total_rows - options[:offset] >= 0) ? > (total_rows - options[:offset]) : 0 > end > sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT > TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + > options[:offset]} ") > sql << ") AS tmp1" > if options[:order] > options[:order] = options[:order].split('','').map do |field| > parts = field.split(" ") > tc = parts[0] > if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used > in query > > > ####-- ORIGINAL : tc.gsub!(/\./, ''\\.\\['') > ####-- ORIGINAL : tc << ''\\]'' > > tc.gsub!(/\./, ''.['') #NEW: -- do not escape > column quotes > tc << '']'' #NEW: -- do not > escape column quotes > > > end > if sql =~ /#{tc} AS (t\d_r\d\d?)/ > parts[0] = $1 > > ####-- ORIGINAL : elsif parts[0] =~ /\w+\.(\w+)/ > elsif parts[0] =~ /\w+\.([\[\]\w]+)/ > #NEW: ensure that column quotes are picked up when matching to strip the > tablename > > parts[0] = $1 > end > parts.join('' '') > end.join('', '') > sql << " ORDER BY > #{change_order_direction(options[:order])}) AS tmp2 ORDER BY > #{options[:order]}" > else > sql << " ) AS tmp2" > end > elsif sql !~ /^\s*SELECT (@@|COUNT\()/i > sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do > "SELECT#{$1} TOP #{options[:limit]}" > end unless options[:limit].nil? > end > end > > > This has fixed the column quoting issue, but there are still some > problems with the SQL that ActiveRecord is generating. The original > issue of selecting by offset now works... > > Article.find( :all, > :offset => 0, > :limit => 1, > :order => "contents.published_at DESC", > :conditions => [''published = ? AND contents.published_at < > ? AND blog_id = ?'',true, Time.now, 1]) > > [#<Article:0x32acb34 @attributes={... (snip) > > And selecting with the :include tag works... > > Article.find( :all, > :order => "contents.published_at DESC", > :include => [:categories, :tags, :user, :blog], > :conditions => [''published = ? AND contents.published_at < > ? AND blog_id = ?'',true, Time.now, 1]) > > [#<Article:0x3848110 @user=#<User:0x38476fc @attributes={... > (snip) > > But a find that combines both the :offset and :include will fail due to > bad SQL... > > Article.find( :all, > :offset => 0, > :limit => 1, > :order => "contents.published_at DESC", > :include => [:categories, :tags, :user, :blog], > :conditions => [''published = ? AND contents.published_at < ? AND > blog_id = ?'',true, Time.now, 1]) > > ActiveRecord::StatementInvalid: DBI::DatabaseError: S0022 (207) > [Microsoft][ODBC SQL Server Driver][SQL Server]Invalid column name > ''published_at''.: SELECT * FROM (SELECT TOP 1 * FROM (SELECT TOP 1 id > FROM contents WHERE (published = 1 AND contents.published_at < > ''2006-07-26 12:24:01'' AND blog_id = 1) AND ( (contents.[type] > ''Article'' ) ) ORDER BY contents.published_at DESC ) AS tmp1 ORDER BY > [published_at] ASC) AS tmp2 ORDER BY [published_at] DESC > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/connection_adapters/abstract_adapter.rb:120:in `log'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/connection_adapters/sqlserver_adapter.rb:521:in `select'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/connection_adapters/sqlserver_adapter.rb:257:in `select_all'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/associations.rb:1166:in `select_limited_ids_list'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/associations.rb:1158:in `add_limited_ids_condition!'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/associations.rb:1148:in > `construct_finder_sql_with_included_associations'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/associations.rb:1111:in `select_all_rows'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/associations.rb:975:in `find_with_associations'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/associations.rb:973:in `find_with_associations'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/base.rb:923:in `find_every'' > from > ./script/../config/../config/../vendor/rails/activerecord/lib/active_rec > ord/base.rb:381:in `find'' > from (irb):104 > from :0 > > This one is caused by just selecting the [id] in the inner select, and > not exposing the the [published_at] column to be sorted on in the outer > selects. Sadly I haven''t been able to get anywhere with debugging this > one. Also, please note that this is being run against the Typo 4 > database, and the test are run via the script/console util. The issues > do also occur through the web frontend. > > Regards, > Stu > > > -----Original Message----- > From: rails-bounces@lists.rubyonrails.org > [mailto:rails-bounces@lists.rubyonrails.org] On Behalf Of Tom Ward > Sent: 25 July 2006 16:18 > To: rails@lists.rubyonrails.org > Subject: Re: [Rails] ActiveRecord SQL Server Adapter Bug... > > Interesting. I''ll take a look this evening. Please let me know if > you find out any more. > > Tom > > On 25/07/06, Stuart Holden <Stuart.Holden@baring-asset.com> wrote: > > Hi, > > > > > > I''ve tried creating a new ticket for this issue, but I''m just getting > > 500 server errors. > > > > There appears to be a problem with the ActiveRecord SqlServer adapter > > that results in illegal SQL being generated... > > > > > > ActiveRecord::StatementInvalid in ArticlesController#index > > DBI::DatabaseError: 37000 (170) [Microsoft][ODBC SQL Server > Driver][SQL > > Server]Line 1: Incorrect syntax near ''\''.: SELECT * FROM (SELECT TOP > 1 > > * > > FROM (SELECT TOP 1 id FROM contents WHERE (published = 1 AND > > contents.published_at < ''2006-07-25 11:59:31'' AND blog_id = 1) AND ( > > (contents.[type] = ''Article'' ) ) ORDER BY contents.published_at DESC > ) > > AS > > tmp1 ORDER BY contents\.\[published_at\] ASC) AS tmp2 ORDER BY > > contents\.\[published_at\] DESC > > > > This is due to messed up column quoting. I''ve tracked it down to the > > ''add_limit_offset!'' method in sqlserver_adapter.rb, but I can''t > decipher > > enough of what is going on to suggest the fix just yet. > > > > Full stack trace also included below. > > > > Regards, > > Stu > > > > RAILS_ROOT: ./script/../config/.. > > Application Trace | Framework Trace | Full Trace > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada > > pters/abstract_adapter.rb:120:in > > `log'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada > > pters/sqlserver_adapter.rb:485:in > > `select'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_ada > > pters/sqlserver_adapter.rb:233:in > > `select_all'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > > b:1166:in > > `select_limited_ids_list'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > > b:1158:in > > `add_limited_ids_condition!'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > > b:1148:in > > `construct_finder_sql_with_included_associations'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > > b:1111:in > > `select_all_rows'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > > b:975:in > > `find_with_associations'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/associations.r > > b:973:in > > `find_with_associations'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:923:in > > `find_every'' > > > > > #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/base.rb:381:in > > `find'' > > #{RAILS_ROOT}/app/controllers/articles_controller.rb:27:in `index'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:910: > > in > > `perform_action_without_filters'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3 > > 68:in > > `perform_action_without_benchmark'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking > > .rb:69:in > > `perform_action_without_rescue'' > > c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/benchmarking > > .rb:69:in > > `perform_action_without_rescue'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/rescue.rb:82 > > :in > > `perform_action'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/base.rb:381: > > in > > `process_without_filters'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/filters.rb:3 > > 77:in > > `process_without_session_management_support'' > > > > > #{RAILS_ROOT}/vendor/rails/actionpack/lib/action_controller/session_mana > > gement.rb:117:in > > `process'' > > #{RAILS_ROOT}/vendor/rails/railties/lib/dispatcher.rb:38:in > `dispatch'' > > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:115:in > > `handle_dispatch'' > > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:81:in > > `service'' > > c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'' > > c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'' > > c:/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'' > > c:/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'' > > c:/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start'' > > c:/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start'' > > c:/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start'' > > c:/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start'' > > #{RAILS_ROOT}/vendor/rails/railties/lib/webrick_server.rb:67:in > > `dispatch'' > > > #{RAILS_ROOT}/vendor/rails/railties/lib/commands/servers/webrick.rb:59 > > c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in > > `require'' > > > > > #{RAILS_ROOT}/vendor/rails/activesupport/lib/active_support/dependencies > > .rb:147:in > > `require'' > > #{RAILS_ROOT}/vendor/rails/railties/lib/commands/server.rb:30 > > c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in > > `require'' > > script/server:3 > > Request > > > > Parameters: None > > > > Show session dump > > --- > > Response > > Headers: {"cookie"=>[], "Cache-Control"=>"no-cache"} > > > > -- > > Ticket URL: <http://www.typosphere.org/trac/ticket/1040> > > typo <http://typosphere.org/trac/> > > web2.0 weblogging engine > > > > > > <body> > > > > <blockquote> > > <font FACE="Arial,Arial" SIZE="1"><p ALIGN="JUSTIFY">This Email may > contain confidential and privileged information and is intended for the > use of the addressee(s) only. If you are not the intended recipient > please notify the sender and delete the Email from your system. It > should not be transmitted to any other person without the consent of the > sender. Additional important notifications regarding Email transmissions > from and to members of Baring Asset Management can be accessed at <a > href="http://www.barings.com/email/index.hcst">http://www.barings.com/em > ail/index.hcst</a></font></p></body> > > </p> > > </font> > > </blockquote> > > > > <p> </p> > > </body> > > </html> > > > > _______________________________________________ > > Rails mailing list > > Rails@lists.rubyonrails.org > > http://lists.rubyonrails.org/mailman/listinfo/rails > > > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails > > > <body> > > <blockquote> > <font FACE="Arial,Arial" SIZE="1"><p ALIGN="JUSTIFY">This Email may contain confidential and privileged information and is intended for the use of the addressee(s) only. If you are not the intended recipient please notify the sender and delete the Email from your system. It should not be transmitted to any other person without the consent of the sender. Additional important notifications regarding Email transmissions from and to members of Baring Asset Management can be accessed at <a href="http://www.barings.com/email/index.hcst">http://www.barings.com/email/index.hcst</a></font></p></body> > </p> > </font> > </blockquote> > > <p> </p> > </body> > </html> > > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails >
Reasonably Related Threads
- establish_connection method
- Rails talking to MySql (cygwin on Windows XP)
- Problem with mongrel_rails, but works with script/server
- what this line means exactly? (@@active_connections ||= {})
- [PATCH] Access to more Arel predicate types from where condition hash