1

my Problem is fairly simple: I got 3 Methods:

  • export function F_SetupDatabase(_logger: any): void
  • export function Q_RunQuery(query: string, db: "session" | "global"): any
  • export function Q_RunQueryWithParams(query: string, params: string[], db: "session" | "global"): any

To Access a Database File (SQLite3). ( Call order: F_SetupDatabase(..) -> Q_RunQuery(...) )

F_SetupDatabase(...) is used to open a Database File global-database.db and create another in-Memory Database session. My main Problem is, that in my App's Main Function, the logs from Q_RunQuery show up before F_SetupDatabase. And Query's also won't work and always return undefined. So i guess it has something to do with async/sync calls or something like that. I tried to avoid async calls/methods completley 'cause i lack the experience and always hang up myself with these. SQL Querys are correct.

Full Source Code of database.ts

/**
 * File: database/database.ts
 * Description: Database Manager 
 * Author: Oliver Karger <kmaster@oliver-karger.de>
 */

import * as sqlite from 'sqlite3';
import chalk from 'chalk';

// can be used in other methods outside this file
export var sessionDatabase: sqlite.Database;
export var database: sqlite.Database;

// Signale Logger
var logger: any;

/**
 * @description Create/Connect Database and Setup in-Memory Session Database`
 * @author Oliver Karger <kmaster@oliver-karger.de>
 * @param {any} _logger Signale Logger
 */
export function F_SetupDatabase(_logger: any): void {
    logger = _logger.scope('Database');
    // Open in-Memory Database
    sessionDatabase = new sqlite.Database(
        ':memory:',
        function (err: any): void {
            if (err) {
                logger.fatal(new Error(err));
            } 
            logger.success('Opened in-Memory Session Database!');
        }
    );
    // Setup in-Memory Database
    // Note: auto_increment not needed, a column: int primary key will auto increment
    // https://stackoverflow.com/questions/24600375/syntax-error-near-autoincrement-with-sqlite-database-creation
    const setupSessionDatabaseQuery: string = `
    create table sessions (
        sessionID int unique,
        clientID varchar(25) not null,
        primary key (sessionID)
    );`
    sessionDatabase.run(setupSessionDatabaseQuery, [], function(err: any){
        if(err) logger.fatal(new Error(err));
        logger.success("Setup of in-Memory Session Database complete!")
    });
    // Open Database File
    database = new sqlite.Database(
        './database/global-database.db', sqlite.OPEN_READWRITE,
        function (err: any): void {
            if (err) {
                logger.fatal(new Error(err));
            }
            logger.success('Opened Database File!');
        }
    );
}

/**
 * @description Run Query without Params
 * @author Oliver Karger <kmaster@oliver-karger.de>
 * @param {string} query SQL Query 
 * @param {"session" | "database"} db On which Database to execute the Query 
 */
export function Q_RunQuery(query: string, db: "session" | "global"): any {
    logger.info("SQL Query: <" + chalk.bold(query) + "> for: " + chalk.bold(db))
    if(db === "global") {
        database.serialize(function() {
             database.all(query, [], function(err: any, rows: any) {
                if(err){
                    logger.fatal(new Error(err));
                }
                return rows;
            });
        });
    } else if(db === "session"){
        sessionDatabase.serialize(function() {
            sessionDatabase.all(query, [], function(err: any, rows: any) {
                if(err){
                    logger.fatal(new Error(err));
                }
                return rows;
            });
        });
    }
}

/**
 * @description Run Query with Params
 * @author Oliver Karger <kmaster@oliver-karger.de>
 * @param {string} query SQL Query 
 * @param {string[]} params SQL Query Params 
 * @param {"session" | "database"} db On which Database to execute the Query 
 */
export function Q_RunQueryWithParams(query: string, params: string[], db: "session" | "global"): any {
    logger.info("SQL Query: <" + chalk.bold(query) + "> for: " + chalk.bold(db))
    if(db === "global") {
        const data =  database.serialize(function() {
            database.all(query, params, function(err: any, rows: any) {
                if(err){
                    logger.fatal(new Error(err));
                }
                return rows;
            });
        });
    } else if(db === "session"){
        return sessionDatabase.serialize(function() {
            sessionDatabase.all(query, params, function(err: any, rows: any) {
                if(err){
                    logger.fatal(new Error(err));
                }
                return rows;
            });
        });
    }
} 

main.ts

export function F_Start(enableLogger?: boolean): http.Server{
    if (enableLogger === true) {
        logger.enable();
    } else if (enableLogger === false) {
        logger.disable();
    } else {
        logger.enable();
    }
    console.clear();
    F_SkynetLogo();
    F_EnableMiddleware(skynetApi, logger);
    F_SetupRoutes(skynetApi, logger);
    F_SetupDatabase(logger);
    skynetServer = F_ServerListener(skynetApi, logger);
    Q_RunQuery(`INSERT INTO sessions (clientID) VALUES ("client-1-0-0")`, "session");
    // Get Session (clientID)
    const session = Q_RunQuery(`SELECT * FROM sessions`, "session");
    console.log(session);
    return skynetServer;
}

Log from Console

[Database] › ℹ  info      SQL Query: <INSERT INTO sessions (clientID) VALUES ("client-1-0-0")> for: session
[Database] › ℹ  info      SQL Query: <SELECT * FROM sessions> for: session
undefined
[Server] › ▶  start     Instance available on: 127.0.0.1:3000
[Database] › ✔  success   Opened in-Memory Session Database!
[Database] › ✔  success   Opened Database File!
[Database] › ✔  success   Setup of in-Memory Session Database complete!

Full Source Code available here: https://github.com/OliverKarger/skynet/tree/development

Oliver Karger
  • 105
  • 1
  • 11

1 Answers1

3

Well, I think it may happen because of the Async code. Each query runs async, so you can not access the result of a query, you need to wait for its execution.

/**
 * @description Run Query without Params
 * @author Oliver Karger <kmaster@oliver-karger.de>
 * @param {string} query SQL Query 
 * @param {"session" | "database"} db On which Database to execute the Query 
 */
export function Q_RunQuery(query: string, db: "session" | "global", callback: CallableFunction): any {
    logger.info("SQL Query: <" + chalk.bold(query) + "> for: " + chalk.bold(db))
    if(db === "global") {
        database.serialize(function() {
             database.all(query, [], function(err: any, rows: any) {
                if(err){
                    callback(err);
                }
                callback(null, rows);
            });
        });
    } else if(db === "session"){
        sessionDatabase.serialize(function() {
            sessionDatabase.all(query, [], function(err: any, rows: any) {
                if(err){
                    callback(err);
                }
                callback(null, rows);
            });
        });
    }
}

In your previous code, you were just returning the value from the callback you passed to the all call to nowhere. Now you can rewrite your code to be.

Q_RunQuery(`SELECT * FROM sessions`, "session", (err, data) => {
    if (err) {
       console.error(err); 
       return;
    }

    console.log('Result, - ', data);
});

UPD: The best way to work with async code is to use Promises, so for example you function can look in the following way.

export function Q_RunQuery<T>(query: string, db: "session" | "global"): Promise<T> {
    logger.info("SQL Query: <" + chalk.bold(query) + "> for: " + chalk.bold(db))
    if(db === "global") {
        return new Promise((res, rej) => {
         database.serialize(function() {
             database.all(query, [], function(err: any, rows: any) {
                if(err){
                    rej(err);
                }
                res(rows);
            });
         });
        })
    } else if(db === "session"){
        // Some thing here...
    }
}
Ayzrian
  • 2,279
  • 1
  • 7
  • 14
  • Thanks man! It worked! I split the function to two seperate ones for "session" and "global" because if: `Function lacks finishing return....`. That was probably 'cause the return-Statements where part of a if-Statement. – Oliver Karger May 11 '21 at 09:14
  • 1
    It was because I have not defined the `else if` part with `Promise` :) I left that for you to do. You are welcome! – Ayzrian May 11 '21 at 09:15