node-mysql2 icon indicating copy to clipboard operation
node-mysql2 copied to clipboard

Element implicitly has an 'any' type because expression of type '0' can't be used to index type 'RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[] | ResultSetHeader'. Property '0' does not exist on type 'RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[] | ResultSetHeader'. When trying to access any index of the result parameter in the callback

Open destroyer22719 opened this issue 3 years ago • 4 comments

Hi there so I have this code

    static async getById(id: string): Promise<Business> {
        return new Promise((resolve, reject) => {
            connection.execute(
                "SELECT * FROM business WHERE ID = ?;",
                [id],
                (err, result) => {
                    if (err) reject(err);
                    const { id, name, email, password }: {id: string, name: string, email: string, password: string} = result[0]; //it complains here
                    resolve({
                        id,
                        name,
                        email,
                        password,
                    });
                }
            );
        });
    }

And I have typescript giving me this error:

Element implicitly has an 'any' type because expression of type '0' can't be used to index type 'RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[] | ResultSetHeader'.
  Property '0' does not exist on type 'RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[] | ResultSetHeader'.ts(7053)

And I'm really not sure what to be doing here because I can't change return type of result

destroyer22719 avatar Jan 09 '22 23:01 destroyer22719

+1, also an issue here got around it by typecasting, but I don't know the library/mysql well so it's not ideal.

			const [matches] = await db.Connection.query(
				"SELECT `Discord UID` FROM `Information` WHERE `Staff Code` = ? OR `Discord UID` = ? OR `Roblox UID` = ? OR `Email`= ?",
				[`${staffInfo.department}-${staffInfo.staffid}`, staffInfo.user.id, userId, staffInfo.email],
			);
			console.log((matches as RowDataPacket[])[0]);

this is with "mysql2/promise"

sasial-dev avatar Jan 23 '22 02:01 sasial-dev

I’m using .execute<RowDataPacket[]>(...) for single-statement selects. Similarly, multi-statement selects would have .execute<RowDataPacket[][]>(...).

I don’t know whether the right type for inserts, updates, creates etc. is ResultSetHeader (which I’m getting in practice) or OkPacket | ResultSetHeader, though.

astiob avatar Apr 19 '22 16:04 astiob

@astiob I'm not an expert in TS but your comment looks right from the protocol point of view: result of query / execute can be

  1. array of rows ( single select statement )
  2. OkPacket | ResultSetHeader ( single insert / delete / update / set variable statement ) ( I'm actually quite confused, they look nearly identical. Need to investigate why we need two )
  3. array of (1|2) in case of multiple statements

sidorares avatar Apr 20 '22 00:04 sidorares

^ not just multiple statements in a single query "select 1; select 2", a prepared statement can also return multiple tables - see https://github.com/sidorares/node-mysql2/blob/dbb344e89a1cc8bb457b24e67b07cdb3013fe844/test/integration/connection/test-binary-multiple-results.js#L77-L100

sidorares avatar Apr 20 '22 00:04 sidorares

Add this if else condition to handle the typescript

if(Array.isArray(result)){
  //your thing
} else {
  //res send status 500
}

1stdeddy2nd avatar Oct 05 '22 07:10 1stdeddy2nd

Hi @destroyer22719, complementing @astiob's answer, you can do it by:

  • Extending mysql.RowDataPacket
  • Using as from TypeScript

Some practical examples:

See the comments

Extending mysql.RowDataPacket (A)
import mysql from 'mysql2';

/** Typing Business and extending the `mysql.RowDataPacket` */
interface Business extends mysql.RowDataPacket {
   id: number;
   name: string;
   email: string;
   password: string;
}

const access: mysql.PoolOptions = {};
const query = 'SELECT * FROM business WHERE ID = ?;';
const params = [1];

mysql.createPool(access).getConnection((_err, connection) => {
   /* Using the Business interface */
   connection.execute<Business[]>(query, params, (_err, result) => {
      const { id, name, email, password } = result[0];

      console.log({
         id,
         name,
         email,
         password,
      });
   });
});
Extending mysql.RowDataPacket (B)
import mysql from 'mysql2';

/** Typing Business and extending the `mysql.RowDataPacket` */
interface Business extends mysql.RowDataPacket {
   id: number;
   name: string;
   email: string;
   password: string;
}

const access: mysql.PoolOptions = {};
const query = 'SELECT * FROM business WHERE ID = ?;';
const params = [1];

mysql.createPool(access).getConnection((_err, connection) => {
   /* Using the Business interface directly in `result` */
   connection.execute(query, params, (_err, result: Business[]) => {
      const { id, name, email, password } = result[0];

      console.log({
         id,
         name,
         email,
         password,
      });
   });
});
Using as from TypeScript (A)
import mysql from 'mysql2';

/** Typing Business */
interface Business {
   id: number;
   name: string;
   email: string;
   password: string;
}

const access: mysql.PoolOptions = {};
const query = 'SELECT * FROM business WHERE ID = ?;';
const params = [1];

mysql.createPool(access).getConnection((_err, connection) => {
   connection.execute(query, params, (_err, result) => {
      /* Using the Business interface from TypeScript `as` */
      const business = result as Business[];
      const { id, name, email, password } = business[0];

      console.log({
         id,
         name,
         email,
         password,
      });
   });
});
Using as from TypeScript (B)
import mysql from 'mysql2';

/** Typing Business */
interface Business {
   id: number;
   name: string;
   email: string;
   password: string;
}

const access: mysql.PoolOptions = {};
const query = 'SELECT * FROM business WHERE ID = ?;';
const params = [1];

mysql.createPool(access).getConnection((_err, connection) => {
   connection.execute(query, params, (_err, result) => {
      /* Using the Business interface from TypeScript `as` directly in `result` */
      const { id, name, email, password } = (result as Business[])[0];

      console.log({
         id,
         name,
         email,
         password,
      });
   });
});

wellwelwel avatar Jun 26 '23 00:06 wellwelwel

@astiob I'm not an expert in TS but your comment looks right from the protocol point of view: result of query / execute can be

  1. array of rows ( single select statement )
  2. OkPacket | ResultSetHeader ( single insert / delete / update / set variable statement ) ( I'm actually quite confused, they look nearly identical. Need to investigate why we need two )
  3. array of (1|2) in case of multiple statements

@sidorares, after reviewing all TypeScript Issues, I noticed that there are many questions, mostly about the use of types in execute and query.

I think it's good to have a "TypeScript Examples" documentation like in documentation/en/Examples.md.

wellwelwel avatar Jun 26 '23 04:06 wellwelwel

Yes, definitely. Even just copying your previous comment to "TypeScript Examples" section would be of great value

sidorares avatar Jun 26 '23 05:06 sidorares