Issues with Fluent PostgreSQL DB creation/Migration

I'm a little bit confused about what I might be doing wrong here when trying to setup my Postgres DB using Vapor 4. I'm trying to create tables a database using the Migration API.

This is just a simple dummy project, just for testing purposes. I have an empty database with no tables, running on the default port of 5432 & I'm using PostgresSQL 14 as well.

Below is my current model setup and their migrations.


// Category Model

final class Category: Model, Content, @unchecked Sendable {
    
    static let schema = Schema.categories

    @ID(key: .id)
    var id: UUID?
    
    @Field(key: "name")
    var name: String
    
    @Field(key: "description")
    var description: String
    
    @Field(key: "created_at")
    var createdAt: Date

    @Children(for: \.$category)
    var questions: [Question]
    
    init() {}
    
    init(id: UUID? = nil,
         name: String,
         description: String,
         createdAt: Date = .now) {
        self.id = id
        self.name = name
        self.description = description
        self.createdAt = createdAt
    }
}

// Category Migration

extension Category {
    
    struct CreateCategory: AsyncMigration {
        
        func prepare(on database: any FluentKit.Database) async throws {
            try await database.schema(Category.schema)
                .id()
                .field("name", .string, .required)
                .create()
        }
        
        func revert(on database: any FluentKit.Database) async throws {
            try await database.schema(Category.schema).delete()
        }
    }
}

// Questions Model

final class Question: Model, Content, @unchecked Sendable {
 
    static let schema = Schema.questions

    @ID(key: .id)
    var id: UUID?
    
    @Field(key: "text")
    var text: String
    
    @Parent(key: "category_id")
    var category: Category
    
    init(){ }
    
    init(id: UUID,
         text: String,
         categoryID: Category.IDValue) {
        self.id = id
        self.text = text
        self.category.id = categoryID
    }
}

// Questions Migration

extension Question {
    
    struct CreateQuestion: AsyncMigration {
        
        func prepare(on database: any FluentKit.Database) async throws {
          try await database.schema(Question.schema)
                .id()
                .field("text", .string, .required)
                .field("category_id", .uuid, .references("category", "id"))
                .create()
        }
        
        func revert(on database: any FluentKit.Database) async throws {
            try await database.schema(Question.schema).delete()
        }
    }
}

I have verified and I'm able to successfully connect to my database within my configure function, and I have also added the migrations to create the tables and any relationships. Since a question can belong to one or more categories. This is the relationship between the two tables.



    guard let host = Environment.get("DATABASE_HOST"),
          let dbPort = Environment.get("DATABASE_PORT"),
          let port = Int(dbPort),
          let username = Environment.get("DATABASE_USERNAME"),
          let password = Environment.get("DATABASE_PASSWORD"),
          let database = Environment.get("DATABASE_NAME") else {
        app.logger.error("Missing ENV configuration")
        throw Abort(.internalServerError, reason: "Database configuration missing")
    }
          
    app.databases.use(
    .postgres(
        configuration: .init(
            hostname: host,
            port: port,
            username: username,
            password: password,
            database: database,
            tls: .disable
        )
    ),
    as: .psql)
    
    app.logger.info("Creating database migration...")
    
    app.migrations.add(Category.CreateCategory.init(), to: .psql)
    app.migrations.add(Question.CreateQuestion.init(), to: .psql)

    app.logger.info("Setting up routes...")
    
    try routes(app)

When I execute the command to run the migration to create the tables

swift run App migrate

I'm getting the following error in the terminal


[ INFO ] [Migrator] Starting prepare [database-id: psql, migration: App.Category.CreateCategory]
[ INFO ] [Migrator] Finished prepare [database-id: psql, migration: App.Category.CreateCategory]
[ INFO ] [Migrator] Starting prepare [database-id: psql, migration: App.Question.CreateQuestion]
[ ERROR ] [Migrator] Failed prepare [database-id: psql, error: PSQLError(code: server, serverInfo: [sqlState: 42P01, file: namespace.c, line: 624, message: relation "category" does not exist, routine: RangeVarGetRelidExtended, localizedSeverity: ERROR, severity: ERROR], triggeredFromRequestInFile: PostgresKit/PostgresDatabase+SQL.swift, line: 57, query: PostgresQuery(sql: CREATE TABLE "questions" ("id" UUID PRIMARY KEY, "text" TEXT NOT NULL, "category_id" UUID REFERENCES "category" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION), binds: [])), migration: App.Question.CreateQuestion]
[ WARNING ] PSQLError(code: server, serverInfo: [sqlState: 42P01, file: namespace.c, line: 624, message: relation "category" does not exist, routine: RangeVarGetRelidExtended, localizedSeverity: ERROR, severity: ERROR], triggeredFromRequestInFile: PostgresKit/PostgresDatabase+SQL.swift, line: 57, query: PostgresQuery(sql: CREATE TABLE "questions" ("id" UUID PRIMARY KEY, "text" TEXT NOT NULL, "category_id" UUID REFERENCES "category" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION), binds: []))
[ ERROR ] Application.shutdown() was not called before Application deinitialized.
Vapor/ServeCommand.swift:131: Assertion failed: ServeCommand did not shutdown before deinit
zsh: trace trap  swift run App migrate

Am I possibly misunderstanding the API? Since following the relation guide my models are super simple and I would of thought this would create two tables, where there is a one to many relationship.

I figured out my issue...

For anyone else having the same problem you need to make sure when you're creating your migration the link for your foreign key is referencing the actual table/schema name. In my case I put category but it didn't exist my table was called categories

I changed

.field("category_id", .uuid, .references("category", "id"))

to

.field("category_id", .uuid, .references("categories", "id"))

2 Likes

One way of solving this is to adopt your own FieldKeys and moving away from stringly typed definitions. There's a good article on Kodeco about it

Admittedly Fluent doesn't make this the most intuitive, things should be better in Fluent 5