tsvector_update_trigger не может найти столбец tsvector

Я работаю над довольно простым блогом на Ruby on Rails и в настоящее время реализую функцию поиска, используя pg_search для полнотекстового поиска PostgreSQL. К сожалению, у меня возникла проблема (я думаю) с моим триггером для обновления столбца tsvector в таблице сообщений. После нескольких часов поиска я не смог решить проблему самостоятельно, хотя я подозреваю, что это легко, если вы делали подобные вещи раньше.

В любом случае ошибка выглядит следующим образом:

irb(main):001:0> post = FactoryGirl.build :post
=> #<Post id: nil, title: "Optimized uniform infrastructure", body: "<p>Inventore consectetur culpa nulla eius voluptati...", published: true, created_at: "2012-09-09  04:08:51", updated_at: nil, tsv: nil>
irb(main):002:0> post.save
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR:  column "tsv" does not exist
    LINE 1: SELECT tsvector_update_trigger(tsv, 'pg_catalog.english', ti...
    QUERY:  SELECT tsvector_update_trigger(tsv, 'pg_catalog.english', title, body)
    CONTEXT:  PL/pgSQL function "posts_before_insert_update_row_tr" line 3 at assignment
    : INSERT INTO "posts" ("body", "created_at", "published", "title", "updated_at") VALUES
    ($1, $2, $3, $4, $5) RETURNING "id"

Столбец tsv создается при миграции следующим образом:

class AddTsvToPosts < ActiveRecord::Migration
    def change
        add_column :posts, :tsv, :tsvector
        add_index(:posts, :tsv, using: 'gin')
    end
end

Триггер определяется с помощью hair_trigger следующим образом:

class Post < ActiveRecord::Base
  include PgSearch

  pg_search_scope :search, against: [:title, :body],
    using: {
      tsearch: {
        dictionary: 'english',
        prefix: true,
        tsvector_column: 'tsv'
      }
    }

  trigger.before(:insert, :update) do
    "new.tsv := tsvector_update_trigger(tsv, 'pg_catalog.english', title, body);"
  end
end

В результате получается следующая схема sql (с config.active_record.schema_format = :sql) -- -- дамп базы данных PostgreSQL --

SET statement_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
SET client_min_messages = warning;

--
-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: -
--

CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;


--
-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';


SET search_path = public, pg_catalog;

--
-- Name: posts_before_insert_update_row_tr(); Type: FUNCTION; Schema: public; Owner: -
--

CREATE FUNCTION posts_before_insert_update_row_tr() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
    new.tsv := tsvector_update_trigger(tsv, 'pg_catalog.english', title, body);
    RETURN NEW;
END;
$$;


SET default_tablespace = '';

SET default_with_oids = false;

--
-- Name: posts; Type: TABLE; Schema: public; Owner: -; Tablespace: 
--

CREATE TABLE posts (
    id integer NOT NULL,
    title character varying(255),
    body text,
    published boolean,
    created_at timestamp without time zone,
    updated_at timestamp without time zone,
    tsv tsvector
);


--
-- Name: posts_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE posts_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;


--
-- Name: posts_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE posts_id_seq OWNED BY posts.id;


--
-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -; Tablespace: 
--

CREATE TABLE schema_migrations (
    version character varying(255) NOT NULL
);


--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY posts ALTER COLUMN id SET DEFAULT nextval('posts_id_seq'::regclass);


--
-- Name: posts_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
--

ALTER TABLE ONLY posts
    ADD CONSTRAINT posts_pkey PRIMARY KEY (id);


--
-- Name: index_posts_on_tsv; Type: INDEX; Schema: public; Owner: -; Tablespace: 
--

CREATE INDEX index_posts_on_tsv ON posts USING gin (tsv);


--
-- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: -; Tablespace: 
--

CREATE UNIQUE INDEX unique_schema_migrations ON schema_migrations USING btree (version);


--
-- Name: posts_before_insert_update_row_tr; Type: TRIGGER; Schema: public; Owner: -
--

CREATE TRIGGER posts_before_insert_update_row_tr BEFORE INSERT OR UPDATE ON posts FOR EACH ROW EXECUTE PROCEDURE posts_before_insert_update_row_tr();


--
-- PostgreSQL database dump complete
--

SET search_path TO "$user",public;

INSERT INTO schema_migrations (version) VALUES ('20131230213035');

INSERT INTO schema_migrations (version) VALUES ('20140115101632');

INSERT INTO schema_migrations (version) VALUES ('20140115183846');

Если бы кто-нибудь помог мне понять, почему рельсы не признают существование столбца tsv, я был бы ооооооо счастлив! :)


person Daniel    schedule 15.01.2014    source источник


Ответы (2)


Если бы кто-нибудь мог помочь мне понять, почему рельсы не признают существование столбца tsv

Это не рельсы, а этот код:

   new.tsv := tsvector_update_trigger(tsv, 'pg_catalog.english', title, body);

Помимо синтаксической проблемы, заключающейся в том, что tsv в этом контексте нельзя интерпретировать, реальная проблема заключается в том, что tsvector_update_trigger возвращает trigger, а не tsvector, поэтому он должен вызываться механизмом SQL после вставки или обновления, а не явным образом кодом пользователя.

На самом деле причина существования уже существующего tsvector_update_trigger заключается именно в том, чтобы программист в первую очередь избегал написания триггера. Ожидается, что вы будете ссылаться на него напрямую с помощью оператора CREATE TRIGGER, например:

CREATE TRIGGER trigger_name 
BEFORE INSERT OR UPDATE ON posts FOR EACH ROW EXECUTE PROCEDURE 
tsvector_update_trigger(tsv, 'pg_catalog.english', title, body);

и тогда ваша функция posts_before_insert_update_row_tr() не нужна.

См. Триггеры для автоматических обновлений в док.

person Daniel Vérité    schedule 15.01.2014
comment
Ах я вижу. И поскольку hair_trigger, похоже, хочет поместить код триггера в свою собственную функцию, у меня остается два варианта: написать оператор триггера самостоятельно или использовать пользовательскую функцию обновления tsvector. Я пробовал оба варианта, но я выберу второй, так как он позволит мне использовать взвешивание для разных столбцов по отдельности. Я опубликую полученный код в отдельном ответе. - person Daniel; 16.01.2014

Основываясь на ответе Даниэля Верите, я написал собственный триггер (поместите его в отдельную миграцию, а не в модель).

class CreateUpdateTsvPostsTrigger < ActiveRecord::Migration
  def up
    create_trigger(compatibility: 1).name('update_tsv_posts').on(:posts).before(:insert, :update) do
      "new.tsv :=
          setweight(to_tsvector('pg_catalog.english', coalesce(new.title,'')), 'A') ||
          setweight(to_tsvector('pg_catalog.english', coalesce(new.body,'')), 'D');"
    end
  end

  def down
    drop_trigger("update_tsv_posts", "posts")
  end
end

Все работает как шарм. :)

person Daniel    schedule 16.01.2014