Skip to content

ColumnNotFound error when querying with MySQL #2206

@ElectrifyPro

Description

@ElectrifyPro

Bug Description

I have this function in my code:

pub async fn fetch_post(&self, id: Option<usize>) -> Option<Post> {
    let query = match id {
        Some(id) => sqlx::query("SELECT id, title, date, text FROM posts WHERE id = ?")
            .bind(id as u64),
        None => sqlx::query("SELECT id, title, date, text FROM posts ORDER BY id DESC"),
    };

    query.fetch_one(&self.pool) // self.pool: sqlx::Pool<sqlx::MySql>
        .await
        .map(|row| match Post::from_row(&row) {
            Ok(post) => post,
            Err(err) => panic!("Failed to parse post: {:?} ({:?})", err, row),
        })
        .ok()
}

Where Post is:

#[derive(sqlx::FromRow)]
pub struct Post {
    id: u32,
    title: String,
    date: PrimitiveDateTime,
    text: String,
}

And the schema of the posts table in my database is (there are several records in this table):

+-------+--------------+------+-----+-------------------+-------------------+
| Field | Type         | Null | Key | Default           | Extra             |
+-------+--------------+------+-----+-------------------+-------------------+
| id    | int unsigned | NO   | PRI | NULL              |                   |
| title | varchar(128) | YES  |     | NULL              |                   |
| date  | datetime     | YES  |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| text  | text         | YES  |     | NULL              |                   |
+-------+--------------+------+-----+-------------------+-------------------+

This is the bug: In my scenario, only the second query (None => sqlx::query("...) usually gets called. At some point, calling that query will start returning an sqlx::Error::ColumnNotFound error is returned instead (notice column_names is empty):

thread 'actix-server worker 0' panicked at 'Failed to parse post: ColumnNotFound("id") (MySqlRow { ..., columns: [MySqlColumn { ordinal: 0, name: id, type_info: ..., flags: ... }, MySqlColumn { ordinal: 1, name: title, type_info: ..., flags: ... }, MySqlColumn { ordinal: 2, name: date, type_info: ..., flags: ... }, MySqlColumn { ordinal: 3, name: text, type_info: ..., flags: ... }],, column_names: {} })'

Because column_names is empty, and indexing into a row relies on the existence of columns in column_names...

// sqlx-core/src/mysql/row.rs
impl ColumnIndex<MySqlRow> for &'_ str {
    fn index(&self, row: &MySqlRow) -> Result<usize, Error> {
        row.column_names // <-- always empty
            .get(*self)
            .ok_or_else(|| Error::ColumnNotFound((*self).into()))
            .map(|v| *v)
    }
}

...Error::ColumnNotFound will keep on being returned. I'd imagine SQLx determines the columns involved in a SELECT query from the output of the database, so it seems like column_names being empty is a bug.

Minimal Reproduction

I know it's incredibly frustrating that I don't have any clear way to reproduce this, since it's such a niche issue...

Info

  • SQLx version: 0.6.2
  • SQLx features enabled: ["runtime-tokio-rustls", "mysql", "uuid", "time"]
  • Database server and version: MySQL 8.0.26
  • Operating system: Linux
  • rustc --version: rustc 1.65.0 (897e37553 2022-11-02)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions