#ruby-on-rails #postgresql #activerecord #ruby-on-rails-4 #rails-activerecord
#ruby-on-rails #postgresql #activerecord #ruby-on-rails-4 #rails-activerecord
Вопрос:
ActiveRecord 4.1.1 генерирует какой-то специфический SQL при сохранении, которым задыхается postgres:
SELECT "projects".* FROM "projects" WHERE ('uid') LIMIT 1
PG::InvalidTextRepresentation: ERROR: invalid input syntax for type boolean: "uid"
Я почти уверен, что «логическое значение», о котором здесь идет речь, на самом деле является WHERE()
синтаксисом, который ожидает логическое выражение в качестве аргумента.
uid
определяется как первичный ключ в моей модели (см. Ниже)… но почему он просто говорит WHERE('uid')
, когда выполняет select_all (см. stacktrace)? (и почему он выполняет select_all при сохранении?)
Рельсовая консоль
p = Project.new(name: "test", description: "test", workflow_id: 1)
=> #<Project name: "test", description: "test", user_id: nil, uid: nil, created_at: nil, updated_at: nil, workflow_id: 1, active: nil>
irb(main):031:0> p.save
(1.9ms) BEGIN
Project Load (5.5ms) SELECT "projects".* FROM "projects" WHERE ('uid') LIMIT 1
PG::InvalidTextRepresentation: ERROR: invalid input syntax for type boolean: "uid"
LINE 1: SELECT "projects".* FROM "projects" WHERE ('uid') LIMIT 1
^
: SELECT "projects".* FROM "projects" WHERE ('uid') LIMIT 1
(1.7ms) ROLLBACK
ActiveRecord::StatementInvalid: PG::InvalidTextRepresentation: ERROR: invalid input syntax for type boolean: "uid"
LINE 1: SELECT "projects".* FROM "projects" WHERE ('uid') LIMIT 1
^
: SELECT "projects".* FROM "projects" WHERE ('uid') LIMIT 1
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/connection_adapters/postgresql_adapter.rb:815:in `async_exec'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/connection_adapters/postgresql_adapter.rb:815:in `block in exec_no_cache'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/connection_adapters/abstract_adapter.rb:373:in `block in log'
from /app/vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.1/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/connection_adapters/abstract_adapter.rb:367:in `log'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/connection_adapters/postgresql_adapter.rb:815:in `exec_no_cache'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/connection_adapters/postgresql/database_statements.rb:137:in `exec_query'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/connection_adapters/postgresql_adapter.rb:947:in `select'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/connection_adapters/abstract/database_statements.rb:31:in `select_all'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/connection_adapters/abstract/query_cache.rb:69:in `select_all'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/querying.rb:39:in `find_by_sql'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/relation.rb:603:in `exec_queries'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/relation.rb:487:in `load'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/relation.rb:231:in `to_a'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/relation/finder_methods.rb:451:in `find_take'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/relation/finder_methods.rb:98:in `take'
... 41 levels...
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/connection_adapters/abstract/database_statements.rb:211:in `block in transaction'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/connection_adapters/abstract/database_statements.rb:219:in `within_new_transaction'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/connection_adapters/abstract/database_statements.rb:211:in `transaction'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/transactions.rb:208:in `transaction'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/transactions.rb:326:in `with_transaction_returning_status'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/transactions.rb:268:in `block in save'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/transactions.rb:283:in `rollback_active_record_state!'
from /app/vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.1/lib/active_record/transactions.rb:267:in `save'
from (irb):31
from /app/vendor/bundle/ruby/2.0.0/gems/railties-4.1.1/lib/rails/commands/console.rb:90:in `start'
from /app/vendor/bundle/ruby/2.0.0/gems/railties-4.1.1/lib/rails/commands/console.rb:9:in `start'
from /app/vendor/bundle/ruby/2.0.0/gems/railties-4.1.1/lib/rails/commands/commands_tasks.rb:69:in `console'
from /app/vendor/bundle/ruby/2.0.0/gems/railties-4.1.1/lib/rails/commands/commands_tasks.rb:40:in `run_command!'
from /app/vendor/bundle/ruby/2.0.0/gems/railties-4.1.1/lib/rails/commands.rb:17:in `<top (required)>'
from /app/bin/rails:4:in `require'
from /app/bin/rails:4:in `<main>
Модель
class Project < ActiveRecord::Base
self.primary_key = "uid"
end
ActiveRecord mixin для генерации идентификаторов
module ActiveRecordUIDExtension
extend ActiveSupport::Concern
def generate_uid
return unless self.class.column_names.include?("uid")
self.id = SecureRandom.random_number(36**10).to_s(36)
while !self.class.find_by(:uid, self.id).nil? do
self.id = SecureRandom.random_number(36**10).to_s(36)
end
end
end
ActiveRecord::Base.send(:include, ActiveRecordUIDExtension)
ActiveRecord::Base.send(:before_create, :generate_uid)
Gemfile.блокировка
activerecord (4.1.1)
activemodel (= 4.1.1)
activesupport (= 4.1.1)
arel (~> 5.0.0)
Комментарии:
1. Ваше предположение об ошибке верно; обратите внимание:
SELECT 1 WHERE ('uid');
производитERROR: invalid input syntax for type boolean: "uid"
.
Ответ №1:
Ваша смесь не имеет никакого смысла. Эта часть прямо здесь вызывает плохой SQL:
self.class.find_by(:uid, self.id)
Это приводит к созданию недопустимого SQL-кода, потому что вы ему это указываете. find_by
имеет гибкий интерфейс, поэтому он должен выполнить некоторый синтаксический анализ, чтобы увидеть, как вы его вызываете. Когда ты говоришь:
find_by(a, b)
синтаксический анализ аргумента, вероятно, предполагает, что вы пытаетесь использовать эту форму:
find_by('some_sql_snippet', placeholder_valid)
как в этом примере из документов:
find_by("published_at < ?", 2.weeks.ago)
Тогда это, вероятно to_s
, будет первый аргумент и просканирует его на наличие заполнителей. Когда вы говорите:
find_by(:uid, self.id)
в итоге вы получите :uid.to_s
и self.id
будете проигнорированы, потому что в этой строке нет заполнителей. Это объясняет, почему вы видите where ('uid')
в SQL.
Исправьте ваше смешивание, чтобы оно имело смысл:
self.class.find_by(:uid => self.id)
или, что еще лучше, переключитесь на exists?
, чтобы избежать создания целого экземпляра модели только для того, чтобы посмотреть, есть ли строка в базе данных:
self.class.where(:uid => self.id).exists?
self.class.exists?(:uid => self.id)
Комментарии:
1. Ага! Какой-то старый код Rails 2, который не перешел на 4 (и которым mysql не подавился)! Большое спасибо за улов!