Add support for explicit PostgreSQL schema prefixes for tests

PostgreSQL tests use pg_temp only. pg_temp is never searched for FTS
objects, so creating then altering an FTS configuration will not work
because PostgreSQL will not be able to find the FTS configuration it
just created.

Instead, we explicitly refer to the FTS objects with their full name
including their prefix, which makes PostgreSQL able to find the object.

This is only needed for tests.

See: https://stackoverflow.com/a/31095452/2347617
See: https://www.postgresql.org/message-id/15191.1208975632@sss.pgh.pa.us
This commit is contained in:
delthas 2023-02-11 13:11:21 +01:00 committed by Simon Ser
parent 1ccc7ce6d2
commit e510f7a461
2 changed files with 26 additions and 16 deletions

View File

@ -122,11 +122,11 @@ CREATE TABLE "MessageTarget" (
UNIQUE(network, target) UNIQUE(network, target)
); );
CREATE TEXT SEARCH DICTIONARY "search_simple_dictionary" ( CREATE TEXT SEARCH DICTIONARY search_simple_dictionary (
TEMPLATE = pg_catalog.simple TEMPLATE = pg_catalog.simple
); );
CREATE TEXT SEARCH CONFIGURATION "search_simple" ( COPY = pg_catalog.simple ); CREATE TEXT SEARCH CONFIGURATION @SCHEMA_PREFIX@search_simple ( COPY = pg_catalog.simple );
ALTER TEXT SEARCH CONFIGURATION "search_simple" ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, hword, hword_part, word WITH "search_simple_dictionary"; ALTER TEXT SEARCH CONFIGURATION @SCHEMA_PREFIX@search_simple ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, hword, hword_part, word WITH @SCHEMA_PREFIX@search_simple_dictionary;
CREATE TABLE "Message" ( CREATE TABLE "Message" (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
target INTEGER NOT NULL REFERENCES "MessageTarget"(id) ON DELETE CASCADE, target INTEGER NOT NULL REFERENCES "MessageTarget"(id) ON DELETE CASCADE,
@ -134,7 +134,7 @@ CREATE TABLE "Message" (
time TIMESTAMP WITH TIME ZONE NOT NULL, time TIMESTAMP WITH TIME ZONE NOT NULL,
sender TEXT NOT NULL, sender TEXT NOT NULL,
text TEXT, text TEXT,
text_search tsvector GENERATED ALWAYS AS (to_tsvector('search_simple', text)) STORED text_search tsvector GENERATED ALWAYS AS (to_tsvector('@SCHEMA_PREFIX@search_simple', text)) STORED
); );
CREATE INDEX "MessageIndex" ON "Message" (target, time); CREATE INDEX "MessageIndex" ON "Message" (target, time);
CREATE INDEX "MessageSearchIndex" ON "Message" USING GIN (text_search); CREATE INDEX "MessageSearchIndex" ON "Message" USING GIN (text_search);
@ -206,11 +206,11 @@ var postgresMigrations = []string{
target TEXT NOT NULL, target TEXT NOT NULL,
UNIQUE(network, target) UNIQUE(network, target)
); );
CREATE TEXT SEARCH DICTIONARY "search_simple_dictionary" ( CREATE TEXT SEARCH DICTIONARY search_simple_dictionary (
TEMPLATE = pg_catalog.simple TEMPLATE = pg_catalog.simple
); );
CREATE TEXT SEARCH CONFIGURATION "search_simple" ( COPY = pg_catalog.simple ); CREATE TEXT SEARCH CONFIGURATION @SCHEMA_PREFIX@search_simple ( COPY = pg_catalog.simple );
ALTER TEXT SEARCH CONFIGURATION "search_simple" ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, hword, hword_part, word WITH "search_simple_dictionary"; ALTER TEXT SEARCH CONFIGURATION @SCHEMA_PREFIX@search_simple ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, hword, hword_part, word WITH @SCHEMA_PREFIX@search_simple_dictionary;
CREATE TABLE "Message" ( CREATE TABLE "Message" (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
target INTEGER NOT NULL REFERENCES "MessageTarget"(id) ON DELETE CASCADE, target INTEGER NOT NULL REFERENCES "MessageTarget"(id) ON DELETE CASCADE,
@ -218,7 +218,7 @@ var postgresMigrations = []string{
time TIMESTAMP WITH TIME ZONE NOT NULL, time TIMESTAMP WITH TIME ZONE NOT NULL,
sender TEXT NOT NULL, sender TEXT NOT NULL,
text TEXT, text TEXT,
text_search tsvector GENERATED ALWAYS AS (to_tsvector('search_simple', text)) STORED text_search tsvector GENERATED ALWAYS AS (to_tsvector('@SCHEMA_PREFIX@search_simple', text)) STORED
); );
CREATE INDEX "MessageIndex" ON "Message" (target, time); CREATE INDEX "MessageIndex" ON "Message" (target, time);
CREATE INDEX "MessageSearchIndex" ON "Message" USING GIN (text_search); CREATE INDEX "MessageSearchIndex" ON "Message" USING GIN (text_search);
@ -226,7 +226,8 @@ var postgresMigrations = []string{
} }
type PostgresDB struct { type PostgresDB struct {
db *sql.DB db *sql.DB
temp bool
} }
func OpenPostgresDB(source string) (Database, error) { func OpenPostgresDB(source string) (Database, error) {
@ -270,7 +271,7 @@ func OpenTempPostgresDB(source string) (Database, error) {
return nil, err return nil, err
} }
db := &PostgresDB{db: sqlPostgresDB} db := &PostgresDB{db: sqlPostgresDB, temp: true}
if err := db.upgrade(); err != nil { if err := db.upgrade(); err != nil {
sqlPostgresDB.Close() sqlPostgresDB.Close()
return nil, err return nil, err
@ -279,6 +280,15 @@ func OpenTempPostgresDB(source string) (Database, error) {
return db, nil return db, nil
} }
func (db *PostgresDB) template(t string) string {
// Hack to convince postgres to lookup text search configurations in
// pg_temp
if db.temp {
return strings.ReplaceAll(t, "@SCHEMA_PREFIX@", "pg_temp.")
}
return strings.ReplaceAll(t, "@SCHEMA_PREFIX@", "")
}
func (db *PostgresDB) upgrade() error { func (db *PostgresDB) upgrade() error {
tx, err := db.db.Begin() tx, err := db.db.Begin()
if err != nil { if err != nil {
@ -304,12 +314,12 @@ func (db *PostgresDB) upgrade() error {
} }
if version == 0 { if version == 0 {
if _, err := tx.Exec(postgresSchema); err != nil { if _, err := tx.Exec(db.template(postgresSchema)); err != nil {
return fmt.Errorf("failed to initialize schema: %s", err) return fmt.Errorf("failed to initialize schema: %s", err)
} }
} else { } else {
for i := version; i < len(postgresMigrations); i++ { for i := version; i < len(postgresMigrations); i++ {
if _, err := tx.Exec(postgresMigrations[i]); err != nil { if _, err := tx.Exec(db.template(postgresMigrations[i])); err != nil {
return fmt.Errorf("failed to execute migration #%v: %v", i, err) return fmt.Errorf("failed to execute migration #%v: %v", i, err)
} }
} }

View File

@ -78,13 +78,13 @@ func TestPostgresMigrations(t *testing.T) {
t.Fatalf("openTempPostgresDB() failed: %v", err) t.Fatalf("openTempPostgresDB() failed: %v", err)
} }
if _, err := sqlDB.Exec(postgresV0Schema); err != nil { db := &PostgresDB{db: sqlDB, temp: true}
defer db.Close()
if _, err := db.db.Exec(postgresV0Schema); err != nil {
t.Fatalf("DB.Exec() failed for v0 schema: %v", err) t.Fatalf("DB.Exec() failed for v0 schema: %v", err)
} }
db := &PostgresDB{db: sqlDB}
defer db.Close()
if err := db.upgrade(); err != nil { if err := db.upgrade(); err != nil {
t.Fatalf("PostgresDB.Upgrade() failed: %v", err) t.Fatalf("PostgresDB.Upgrade() failed: %v", err)
} }