TryGhost / node-sqlite3

SQLite3 bindings for Node.js
BSD 3-Clause "New" or "Revised" License
6.23k stars 817 forks source link

IOT instruction node testing.js #1787

Open whitedotjsx opened 6 months ago

whitedotjsx commented 6 months ago

Issue Summary

Error trying to make a database module for my app

Im trying to create a database module so every component of my app can import it and get functions like setId, getId, deleteId but im getting this error when trying to run it wherever the module itself or importing it.

Already tried to reinstall node, npm and removed node_modules

Node version: v20.13.1 Sqlite3 version: 5.1.7 OS: WSL running Ubuntu distro under Windows x64

➜  DBM git:(origin) ✗ node --inspect --trace-warnings testing.js
Debugger listening on ws://127.0.0.1:9229/d6da2615-e1e4-4412-920f-8c8420a262b8
For help, see: https://nodejs.org/en/docs/inspector
>>> 12-05-2024 12:38:58 America/Bogota [DEBUG] Current database path: /mnt/c/Code/Asuka/src/db/presetsDb.db
Promise { <pending> }
>>> 12-05-2024 12:38:58 America/Bogota [DEBUG] Database instance has been started.
FATAL ERROR: Error::New napi_get_last_error_info
----- Native stack trace -----

 1: 0xb82b0c node::OnFatalError(char const*, char const*) [node]
 2: 0xc756f5  [node]
 3: 0x7fcb4d3b2a1e  [/mnt/c/Code/Asuka/node_modules/sqlite3/build/Release/node_sqlite3.node]
 4: 0x7fcb4d3b2d5d Napi::Error::New(napi_env__*) [/mnt/c/Code/Asuka/node_modules/sqlite3/build/Release/node_sqlite3.node]
 5: 0x7fcb4d3d0ec0 node_sqlite3::Statement::Finalize_() [/mnt/c/Code/Asuka/node_modules/sqlite3/build/Release/node_sqlite3.node]
 6: 0x7fcb4d3d8458 Napi::ObjectWrap<node_sqlite3::Statement>::ConstructorCallbackWrapper(napi_env__*, napi_callback_info__*) [/mnt/c/Code/Asuka/node_modules/sqlite3/build/Release/node_sqlite3.node]
 7: 0xc4f049  [node]
 8: 0xf5596f v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) [node]
 9: 0xf55f25  [node]
10: 0xf56643 v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) [node]
11: 0x1960df6  [node]

----- JavaScript stack trace -----

1: /mnt/c/Code/Asuka/node_modules/sqlite3/lib/sqlite3.js:19:27
2: object.<computed> (/mnt/c/Code/Asuka/node_modules/sqlite3/lib/trace.js:28:20)
3: /mnt/c/Code/Asuka/src/internal/DBM/DatabaseModule.js:76:10
4: createListsTable (/mnt/c/Code/Asuka/src/internal/DBM/DatabaseModule.js:75:12)
5: /mnt/c/Code/Asuka/src/internal/DBM/DatabaseModule.js:52:13

[1]    16303 IOT instruction  node --inspect --trace-warnings testing.js

I get this sometimes too:

malloc(): invalid next size (unsorted)
[1]    14318 IOT instruction  node testing.js

The module code(Any suggestion is apprecciated)

// sqlite3 library
const sqlite3 = require("sqlite3").verbose();

// native node modules
const appRoot = require('app-root-path');
const rootPath = appRoot.path;
const path = require("node:path");

// local modules
const { log } = require("../../modules/essentials");
const { presetsDbPath } = require('../../../config.json');

/**
 * This function will take some environment variables and check validations before throwing the path of the database that will be used
 * for the *presets* DBM.
 * @returns {string}
 */
function loadDbPath() {
  try {
    // check if channelsDbPath is defined in config.json
    if (presetsDbPath) {
      return path.join(rootPath, presetsDbPath);
    } else {
      throw new Error("The channelsDbPath value is not defined correctly in the config.json file.");
    }
  } catch (error) {
    log.error(`Error loading database path: ${error.message}. Asuka is using the default database path until it's correctly assigned again.`);
    return path.join(rootPath, 'src', 'db', 'channelsDb.db');
  }
}

/**
 * Database initialization, this function will only be called ONE TIME and then {@link dbInstance} will handle and return the database connections for better efficiency.
 * @returns {object}
 */
async function dbInit() {
  const DB_PATH = loadDbPath();

  log.debug("Current database path:", DB_PATH);

  const dbModule = require("../schemas/dbSchema.json");

  const db = new sqlite3.Database(
    DB_PATH, async (err) => {
      if (err) {
        log.error("Error trying to open local database:", err.message);
        console.error(err);
      } else {
        db.run('PRAGMA foreign_keys = ON;');
        try {
          await Promise.all([
            createListsTable(db, dbModule.users),
            createListsTable(db, dbModule.servers),
            createListsTable(db, dbModule.roles),
            createListsTable(db, dbModule.rolesToGive),
            createListsTable(db, dbModule.rolePermissions),
            createListsTable(db, dbModule.channels)
          ]);
          console.log("All tables are OK.");
        } catch (error) {
          log.error("Error creating tables:", error);
          log.error(error);
        }
      }
    }
  );

  /**
   * Creates a table with actually sql into the db given, just for automatization. *I know i can optimize this, but meh.*
   * @param {object} db - SQLITE3 Database connection Object.
   * @param {string} code - Arbitrary SQL query.
   * @returns {Promise<void>}
   */
  async function createListsTable(db, code) {
    return new Promise((resolve, reject) => {
      db.run(code,
        (err) => {
          if (err) {
            log.error(`Error trying to create table:`, err);
            reject(err);
          }
        }
      );
    });
  }

  return db;

}

let db;

/**
 * `Handles database connections`, for efficiency this will return a database connection and if there is not one yet, **will open it and hold it so you will get the same connection** or if the database is starting this will check every 150 milliseconds to return the connection already made.
 * @returns {Promise<object>}
 */
async function dbInstance() {
  let dbi;
  async function waitFor() {
    return new Promise(resolve => {
      setTimeout(() => {
        if (db === 1) {
          waitFor().then(resolve());
        } else {
          resolve(db);
        }
      }, 100);
    });
  }

  if (db === 1) {
    dbi = await waitFor();
    return dbi;
  } else {
    if (typeof db !== 'object') {
      db = 1;
      db = await dbInit();
      dbi = db;
      log.debug('Database instance has been started.');
      return dbi;
    } else {
      return db;
    }
  }
}

module.exports = dbInstance;

Steps to Reproduce

Running any query to the database or a test like this using nodejs

const DBM = require('./dbInteraction');

DBM.setId(1, 1, 1, { userRole: "Test"});

Code:

const dbInstance = require("./DatabaseModule");

/**
 * **Manage the function of setting up identifiers and objects inside the database of Asuka instances and presets**. Also called `DBM`. _(Database Manager/Module.)_.
 * DBM requires the *main configuration file appended to be accordingly set and mounted with working paths* ~or~ the _default internal configuration will override the wrong preset given by the user from the config.json file_.
 * @param {number} type - **Range between 1-5.** _you should give specific properties_ inside the {@link options} parameter required for this function for each different number in this range.
 * ```
 * 1: user<?userRole>, 2: role<roleName, ?roleColor>, 3: channel<channelName, channelType, ?isNsfw>, 4: server<isPrivate, ?serverName>, 5: roleToGive<userId>
 * ```
 * @param {number} id - _This is the main ID to insert into the database_, **it will be checked before being inserted** and this number `depends` on the range given by {@link type}
 * ```
 * 1: user ID expected. 2: role ID expected. 3: channel ID expected. 4: server ID expected.
 * ```
 * _5: role Id expected and an user ID inside the options parameter with the key userId._ {@link options}
 * @param {number} serverId - Server ID where _the actions are being taken_. This can be `0` if it is not related to ~an action being taken inside a server~. _(ex. like inserting an id of a server)_.
 * Otherwise the function will throw an error.
 * @param {Object} options - **Optional extra parameters**. _The properties of this object will be interpreted as the {@link type}_ param says. This parameter WILL require one or more properties by each range given by the type param.
 * @param {string} [options.userRole] - `?Optional` This is just a common name for _searching a user inside the database_.
 * @param {string} [options.roleName] - `<Required>` This is the **name of the role** for setting it into the DB. Anyways, if the name is modified inside Discord, `the bot will adjust permissions and functions ignoring if the name is changed`.
 * **Maximum characters**: 2000
 * @param {string} [options.roleColor] - `?Optional` This is the hexadecimal color of the role, is none is given, then just no color is being applied.
 * @param {string} [options.channelName] - `<Required>` The channel _name of the channel_ to build. 
 * @param {number} [options.channelType] - `<Required>` The _type of the channel_ to build. **Range between 0-4**.
 * ```
 * 0: category, 1: text, 2: voice, 3: stage
 * ```
 * @param {number} [options.isNsfw] - `?Optional` Works like a boolean, will define if the channel to build is NSFW or SFW. **Range between 0-1**.
 * ```
 * 0: false, 1: true
 * ```
 * @param {number} [options.isPrivate] - `<Required>` This will _define is the server to build should be treated like a private or a public server_. This is required for **logic reasons**.
 * ```
 * Works like a boolean. 0: false, 1: true
 * ```
 * @param {string} [options.serverName] - `<Required>` The name of the server to build, **Min to max characters**: 2-32.
 * @param {number} [options.userId] - `<Required>` This will **define the user ID to give the role given by {@link id}**. _Just for synchronization and sequential actions_.
 * @returns {Promise<object>} 
 */
async function setId(type, id, serverId, options) {
  const db = await dbInstance();
  if (
    (!options || typeof options !== "object") ||
    (!type || typeof type !== "number") ||
    (!id || typeof id !== "number") ||
    (!serverId || typeof serverId !== "number")
  ) {
    throw new Error("Invalid parameters. (src/modules/presetsDb.js:setId())");
  }

  let table;

  /**
   * Type 1 validations.
   */
  if (type === 1) {
    if (options.userRole) {
      if (typeof options.userRole !== "string") {
        throw new Error("Invalid userRole type inside the options parameter. (src/modules/presetsDb.js:setId())");
      }
    }
  }
  /**
   * Type 2 validations.
   */
  if (type === 2) {
    if (options.roleName) {
      if (options.roleColor) {
        if (typeof options.roleColor !== "number") {
          throw new Error("Invalid roleColor type inside the options parameter. (src/modules/presetsDb.js:setId())");
        }
      }
    } else {
      throw new Error("roleName property was not present inside the options parameter. (src/modules/presetsDb.js:setId())");
    }
  }
  /**
   * Type 3 validations.
   */
  if (type === 3) {
    if (options.channelName) {
      if (typeof options.channelName !== "string") {
        throw new Error("Invalid channelName type inside the options parameter. (src/modules/presetsDb.js:setId())");
      }
      if (options.channelType) {
        if (options.channelType !== "number") {
          throw new Error("Invalid channelType type inside the options parameter. (src/modules/presetsDb.js:setId())");
        }
        if (options.isNsfw && typeof options.isNsfw !== "number") {
          throw new Error("Invalid isNsfw type inside the options parameter. (src/modules/presetsDb.js:setId())");
        }
      } else {
        throw new Error("channelType property was not present inside the options parameter. (src/modules/presetsDb.js:setId())");
      }
    } else {
      throw new Error("channelName property was not present inside the options parameter. (src/modules/presetsDb.js:setId())");
    }
  }
  /**
   * Type 4 validations.
   */
  if (type === 4) {
    if (options.isPrivate) {
      if (typeof options.isPrivate !== "number") {
        throw new Error("Invalid isPrivate datatype inside the options parameter. Number expected. (src/modules/presetsDb.js:setId())")
      }
    } else {
      throw new Error("isPrivate property was not present inside the options parameter. (src/modules/presetsDb.js:setId())");
    }
    if (options.serverName && typeof options.serverName !== "string") {
      throw new Error("Invalid serverName property inside the options parameter. Expected string. (src/modules/presetsDb.js:setId())");
    }
  }
  /**
   * Type 5 validations.
   */
  if (type === 5) {
    if (options.userId) {
      if (typeof options.userId !== "number") {
        throw new Error("Invalid userId property inside of the options parameter. Expected number. [Type 5](src/modules/presetsDb.js:setId())")
      }
    } else {
      throw new Error("userId was not present inside the options parameter. [Type 5](src/modules/presetsDb.js:setId())")
    }
  }

  let sql;
  let columns;
  const date = new Date();

  switch (type) {
    case 1:
      table = 'users';
      if (options.userRole) {
        sql = `${id}, '${options.userRole}', '${date}'`;
        columns = 'userId, role, createdAt';
      } else {
        sql = `${id}, '${date}'`;
        columns = 'userId, createdAt';
      }
      db.run(`INSERT INTO users (${columns}) VALUES(${sql})`, (err) => {
        if (err) {
          throw new Error(err.message);
        }
      });

      break;
    case 2:
      table = 'roles';
      if (options.roleColor) {
        sql = `${id}, ${options.roleName}, ${options.roleColor}, ${date}`;
        columns = 'roleId, roleName, roleColor, createdAt';
      } else {
        sql = `${id}, ${options.roleName}, ${date}`;
        columns = 'roleId, roleName, createdAt';
      }
      db.run(`INSERT INTO roles (${columns}) VALUES (?)`, [sql], (err) => {
        throw new Error(err);
      })
      break;
    case 3:
      table = 'channels';
      if (options.isNsfw) {
        sql = `${id}, ${options.channelType}, ${options.channelName}, ${options.isNsfw}, ${date}`;
        columns = 'channelId, channelType, channelName, isNsfw, createdAt';
      } else {
        sql = `${id}, ${options.channelType}, ${options.channelName}, ${date}`;
        columns = 'channelId, channelType, channelName, createdAt';
      }
      db.run(`INSERT INTO channels (${columns}) VALUES (?)`, [sql], (err) => {
        throw new Error(err);
      })
      break;
    case 4:
      table = 'servers';
      if (options.serverName) {
        sql = `${id}, ${options.isPrivate}, ${options.serverName}, ${date}`;
        columns = 'serverId, isPrivate, serverName, createdAt';
      } else {
        sql = `${id}, ${options.isPrivate} ${date}`;
        columns = 'serverId, isPrivate, createdAt';
      }
      db.run(`INSERT INTO servers (${columns}) VALUES (?)`, [sql], (err) => {
        throw new Error(err);
      })
      break;
    case 5:
      table = "rolesToGive";
      sql = `${id}, ${options.userId}, ${date}`;
      columns = 'roleId, userId, createdAt'
      db.run(`INSERT INTO rolesToGive (${columns}) VALUES (?)`, [sql], (err) => {
        throw new Error(err);
      })
      break;
    default:
      throw new Error('Invalid type. Only 0-5 types accepted. [Invalid type](src/modules/presetsDb.js:setId())');
  }
  return table;
}

/**
 * `Retrieves an object of the information about an ID from the Database`. It can be null if not found the ID given into the specified table/type.
 * @param {number} type - `Range between 0-5.`
 * ```
 * 1: user<userId, role, createdAt>, 2: role<roleId, roleName, roleColor, createdAt>, 3: channel<channelId, channelName, channelType, isNsfw, createdAt>, 4: server<serverId, isPrivate, serverName, createdAt>, 5: roleToGive<roleId, userId, createdAt>
 * ```
 * @param {number} id - The ID to search.
 * @returns {Promise<null | object>}
 */
async function getId(type, id) {
  const db = await dbInstance();
  if (
    (!type || typeof type !== "number") ||
    (!id || typeof id !== "number")
  ) {
    throw new TypeError("Invalid parameters.");
  }

  let query;
  switch (type) {
    case 1:
      db.get(`SELECT * FROM users WHERE userId = ?`, [id], (err, row) => {
        if (err) {
          throw new Error(err.message);
        }
        query = row;
      });
      break;
    case 2:
      db.get(`SELECT * FROM roles WHERE roleId = ?`, [id], (err, row) => {
        if (err) {
          throw new Error(err.message);
        }
        query = row;
      });
      break;
    case 3:
      db.get(`SELECT * FROM channels WHERE channelId = ?`, [id], (err, row) => {
        if (err) {
          throw new Error(err.message);
        }
        query = row;
      });
      break;
    case 4:
      db.get(`SELECT * FROM servers WHERE serverId = ?`, [id], (err, row) => {
        if (err) {
          throw new Error(err.message);
        }
        query = row;
      });
      break;
    case 5:
      db.all(`SELECT * FROM rolesToGive WHERE roleId = ?`, [id], (err, rows) => {
        if (err) {
          throw new Error(err);
        }
        query = rows;
      });
      break;
    default:
      throw new Error('Invalid type. Only 0-5 types accepted. [Invalid type](src/modules/presetsDb.js:setId())');
  }
  return query;
}

/**
 * `Deletes an object with the specified ID from the Database`.
 * @param {number} type - `Range between 1-5.`
 * ```
 * 1: user, 2: role, 3: channel, 4: server, 5: roleToGive
 * ```
 * @param {number} id - The ID to delete.
 * @returns {Promise<object>}
 */
async function deleteId(type, id) {
  const db = await dbInstance();
  if (
    (!type || typeof type !== "number") ||
    (!id || typeof id !== "number")
  ) {
    throw new TypeError("Invalid parameters.");
  }

  let query;
  switch (type) {
    case 1:
      query = await db.run(`DELETE FROM users WHERE userId = ?`, [id]);
      break;
    case 2:
      query = await db.run(`DELETE FROM roles WHERE roleId = ?`, [id]);
      break;
    case 3:
      query = await db.run(`DELETE FROM channels WHERE channelId = ?`, [id]);
      break;
    case 4:
      query = await db.run(`DELETE FROM servers WHERE serverId = ?`, [id]);
      break;
    case 5:
      query = await db.run(`DELETE FROM rolesToGive WHERE roleId = ?`, [id]);
      break;
    default:
      throw new Error('Invalid type. Only 1-5 types accepted.');
  }
  return query;
}

module.exports = {
  setId,
  getId,
  deleteId
}

Version

5.1.7

Node.js Version

v20.13.1

How did you install the library?

npm install sqlite3 with npm 10.5.2

arch: 'x64', platform: 'linux', release: { name: 'node', lts: 'Iron' }