fix(progress): перенести профильную миграцию на V13 после аттестаций
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
-- Автор комментария и последнего изменения оценки — для профиля студента курса.
|
||||
-- Старые записи остаются без автора и отображаются как «автор не указан».
|
||||
ALTER TABLE grade_comments ADD COLUMN author_account_id uuid;
|
||||
ALTER TABLE lab_grades ADD COLUMN updated_by_account_id uuid;
|
||||
|
||||
-- Комментарии уже индексированы grade_comments_grade_idx (student_id, lab_id).
|
||||
CREATE INDEX lab_grades_lab_idx ON lab_grades (lab_id);
|
||||
@@ -0,0 +1,12 @@
|
||||
-- V13: миграция профиля студента курса.
|
||||
-- Номер V13, потому что V11 (attestations) и V12 (attestation_semester_and_publication)
|
||||
-- уже существуют в master и применены в production; их порядок нельзя нарушать,
|
||||
-- а V10 не может быть вставлена перед ними.
|
||||
-- Автор комментария и последнего изменения оценки — для профиля студента курса.
|
||||
-- Старые записи остаются без автора и отображаются как «автор не указан».
|
||||
-- IF NOT EXISTS: безопасно, если старая feature-миграция V10 уже была применена вручную/dev.
|
||||
ALTER TABLE grade_comments ADD COLUMN IF NOT EXISTS author_account_id uuid;
|
||||
ALTER TABLE lab_grades ADD COLUMN IF NOT EXISTS updated_by_account_id uuid;
|
||||
|
||||
-- Комментарии уже индексированы grade_comments_grade_idx (student_id, lab_id).
|
||||
CREATE INDEX IF NOT EXISTS lab_grades_lab_idx ON lab_grades (lab_id);
|
||||
@@ -0,0 +1,115 @@
|
||||
package ru.ysu.lms.progress.migration
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
|
||||
import org.springframework.core.io.ClassPathResource
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.jdbc.datasource.init.ScriptUtils
|
||||
import org.testcontainers.junit.jupiter.Container
|
||||
import org.testcontainers.junit.jupiter.Testcontainers
|
||||
import org.testcontainers.postgresql.PostgreSQLContainer
|
||||
import java.sql.PreparedStatement
|
||||
import java.sql.Statement
|
||||
import javax.sql.DataSource
|
||||
|
||||
/**
|
||||
* V13 идемпотентна: повторный запуск не должен падать, а колонки и индекс
|
||||
* остаются в ожидаемом состоянии. Реальная PostgreSQL 18 (Testcontainers).
|
||||
*/
|
||||
@Testcontainers
|
||||
@SpringBootTest
|
||||
class ProfileAuthorsMigrationTest {
|
||||
companion object {
|
||||
@Container
|
||||
@ServiceConnection
|
||||
@JvmStatic
|
||||
val postgres = PostgreSQLContainer("postgres:18")
|
||||
|
||||
private const val COLUMN_QUERY = """
|
||||
SELECT data_type, is_nullable
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'progress'
|
||||
AND table_name = ?
|
||||
AND column_name = ?
|
||||
"""
|
||||
|
||||
private const val INDEX_QUERY = """
|
||||
SELECT i.indisvalid, pg_get_indexdef(i.indexrelid) AS definition
|
||||
FROM pg_index i
|
||||
JOIN pg_class c ON c.oid = i.indexrelid
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE n.nspname = 'progress'
|
||||
AND c.relname = 'lab_grades_lab_idx'
|
||||
"""
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private lateinit var dataSource: DataSource
|
||||
|
||||
@Test
|
||||
fun `V13 profile authors migration is idempotent`() {
|
||||
val migration = ClassPathResource("db/migration/V13__profile_authors.sql")
|
||||
|
||||
repeat(2) { applyMigration(migration) }
|
||||
|
||||
assertThat(column("grade_comments", "author_account_id"))
|
||||
.isEqualTo("uuid" to "YES")
|
||||
assertThat(column("lab_grades", "updated_by_account_id"))
|
||||
.isEqualTo("uuid" to "YES")
|
||||
|
||||
val index = labIndex()
|
||||
assertThat(index).isNotNull
|
||||
assertThat(index!!.first).isTrue()
|
||||
assertThat(index.second).contains("lab_grades").contains("lab_id")
|
||||
}
|
||||
|
||||
private fun applyMigration(migration: Resource) {
|
||||
dataSource.connection.use { connection ->
|
||||
// Flyway работает в схеме progress; сырой JDBC-коннект её не видит.
|
||||
connection.createStatement().use { statement ->
|
||||
statement.execute("SET search_path TO progress, public")
|
||||
}
|
||||
ScriptUtils.executeSqlScript(connection, migration)
|
||||
}
|
||||
}
|
||||
|
||||
private fun column(
|
||||
table: String,
|
||||
column: String,
|
||||
): Pair<String, String>? =
|
||||
dataSource.connection.use { connection ->
|
||||
connection.prepareStatement(COLUMN_QUERY.trimIndent()).use { statement ->
|
||||
statement.setString(1, table)
|
||||
statement.setString(2, column)
|
||||
readColumn(statement)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readColumn(statement: PreparedStatement): Pair<String, String>? =
|
||||
statement.executeQuery().use { rows ->
|
||||
if (rows.next()) {
|
||||
rows.getString("data_type") to rows.getString("is_nullable")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun labIndex(): Pair<Boolean, String>? =
|
||||
dataSource.connection.use { connection ->
|
||||
connection.createStatement().use { statement ->
|
||||
readIndex(statement)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readIndex(statement: Statement): Pair<Boolean, String>? =
|
||||
statement.executeQuery(INDEX_QUERY.trimIndent()).use { rows ->
|
||||
if (rows.next()) {
|
||||
rows.getBoolean("indisvalid") to rows.getString("definition")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user