Seti
Seti

Reputation: 2314

Property 'length' does not exists on type ... OkPacket in mysql2 module

I have code similar to this - very simple just to show the case.

this.getCode = (code: string): Promise<codeObject | false> => {
        return new Promise((resolve, reject) => {
            pool.query('SELECT * FROM ?? WHERE code = ?', [TABLE, code], function (err, result) {
                if (err) {
                    return reject(err);
                }

                if (result.length === 0) {
                    return resolve(false);
                }
            });
        });
    };

Problem is in line if (result.length === 0) {, the error is error TS2339: Property 'length' does not exist on type 'RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[]'.

I can not find any way to redefine OkPacket to at least have length?: number that would be enought for ignore that error (most selects do not get OkPackets anyway and i dont wanna have to check type on each select if what i got is not OkPacket when i know it isnt)...

Upvotes: 7

Views: 5553

Answers (6)

Zei
Zei

Reputation: 413

Major oversight in the design of the mysql2 typing. The problem is, as the error suggests, that mysql2 defines 4 possible return types for query(). RowDataPacket[] is for individual SELECT queries that return a set of rows. RowDataPacket[][] is used when you have multiple select queries within one statement, and each returns a set of rows. Similarly OkPacket returns the result of an INSERT or UPDATE or any other operation, and OkPacket[] is returned when there are multiple queries of those types in one statement.

So what's happening is, Typescript has no idea which of the 4 types are going to be returned when you execute your query. In order to resolve this, you must specify which one will be returned.

First of all, you need to import the relevant types from the mysql2 package. So start with the following in your database file

import mysql from "mysql2/promise";

const connection = mysql.createPool({
    ...
});

export type RowDataPacket = mysql.RowDataPacket;
export type OkPacket = mysql.OkPacket;
export default connection;

Then when you make your queries, do

import db, { RowDataPacket } from "../db.server";

const [existingUser] = await db.execute<RowDataPacket[]>(`
    SELECT UserID FROM User WHERE Email = ?
`, [email]);

if (existingUser.length) console.log(`User already exists`);

And similarly with multiple select queries,

import db, { RowDataPacket } from "../db.server";

const [existingUsers] = await db.execute<RowDataPacket[][]>(`
    SELECT UserID FROM User WHERE Email = ?;
    SELECT UserID FROM User WHERE Email = ?;
`, [email0, email1]);

if (existingUsers[0].length || existingUsers[1].length) console.log(`User already exists`);

It's annoying, but this is the actual proper way to do it.

Upvotes: 0

Gogol
Gogol

Reputation: 3070

For my own use case, the map was not defined in OkPacket, because I needed the following

function (error, results, fields) {

            const data = results.map(() => {
                //... do stuffs..
                return {
                    name: item.name,
                    username: item.username,
                    image: item.image,
                    bio: item.bio,
                    url: item.url,
                }
            })
            resolve([results, fields]);
}

To get rid of the error, I ended up checking if map exists in the results object, and making an early return if not (just the reject() won't fix the error). Modified the code as following

function (error, results, fields) {
            if(!('map' in results)) {
                reject(results);
                return;
            }
            const data = results.map((item) => {
                // ...do stuff...
                return {
                    name: item.name,
                    username: item.username,
                    image: item.image,
                    bio: item.bio,
                    url: item.url,
                }
            })
            resolve([results, fields]);
}

For your case you could check for the length, for example, and make an early return if it does not exist.

if(!('length' in results)) {
     reject(results);
     return;
}

Upvotes: 0

Jahnuel Dorelus
Jahnuel Dorelus

Reputation: 171

I faced a similar issue and was able to fix the warning that TypeScript gives, "Property 'length' does not exist on type 'OkPacket'" by checking to see if the resulting rows from the query was an array. This was a lot easier for me to do than converting the rows to a string with JSON.stringify().

Using the original example above, I'd change the result.length comparison to this:

if (Array.isArray(result) && result.length === 0) {
   return resolve(false);
}

Upvotes: 11

Romain Bruckert
Romain Bruckert

Reputation: 2610

I also dislike the design choice on this, but i usually use :

const [rows, fields] = await conn.execute(QUERY_STRING) as any
console.log(rows.length)

Upvotes: 0

I hate this error as well. This is my workaround:

const [rows] = await db.query(`mySqlHere`);
let tempResult: any = rows;

console.log(tempResult.length); // OK

Upvotes: 0

Alexander Reshytko
Alexander Reshytko

Reputation: 2236

I couldn't find a proper way to handle this either, so ended up with this workaround for my similar case:

const [rows,] = await connection.execute("SELECT id FROM users WHERE email = ?", [profile.email]);
if ((rows as any).length === 1) {
   return (rows as any)[0].id;
}
logger.error("Unexpected query response", rows);
throw Error("This code should never be executed");

You can also use user defined type-guards. Like this:

const isRowDataPacket = (rows: RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[] | ResultSetHeader): rows is RowDataPacket[] | RowDataPacket[][] => {
    return (rows as RowDataPacket[] | RowDataPacket[][]).length !== undefined
}

But you would still need to make a typecast further to interpret each row correctly:

const [rows,] = await connection.execute("SELECT id FROM users WHERE email = ?", [profile.email]);
if (isRowDataPacket(rows) && rows.length === 1) {
    return (rows[0] as { id: string }).id;
}

Upvotes: 1

Related Questions