Where to store SQL commands for execution

后端 未结 12 1471
后悔当初
后悔当初 2021-02-05 16:00

We face code quality issues because of inline mysql queries. Having self-written mysql queries really clutters the code and also increases code base etc.

Our code is clu

12条回答
  •  南方客
    南方客 (楼主)
    2021-02-05 16:32

    Another approach with separate files by using ES6 string templates.

    Of course, this doesn't answer the original question because it requires ES6, but there is already an accepted answer which I'm not intending to replace. I simply thought that it is interesting from the point of view of the discussion about query storage and management alternatives.

    // myQuery.sql.js
    "use strict";
    
    var p = module.parent;
    var someVar = p ? '$1' : ':someVar'; // Comments if needed...
    var someOtherVar = p ? '$2' : ':someOtherVar';
    
    module.exports = `
    --@@sql@@
        select foo from bar
        where x = ${someVar} and y = ${someOtherVar}
    --@@/sql@@
    `;
    
    module.parent || console.log(module.exports);
    // (or simply "p || console.log(module.exports);")
    

    NOTE: This is the original (basic) approach. I later evolved it adding some interesting improvements (BONUS, BONUS 2 and FINAL EDIT sections). See the bottom of this post for a full-featured snipet.

    The advantages of this approach are:

    • Is very readable, even the little javascript overhead.

      • It also can be properly syntax higlighted (at least in Vim) both javascript and SQL sections.
    • Parameters are placed as readable variable names instead of silly "$1, $2", etc... and explicitly declared at the top of the file so it's simple to check in which order they must be provided.

    • Can be required as myQuery = require("path/to/myQuery.sql.js") obtaining valid query string with $1, $2, etc... positional parameters in the specified order.

    • But, also, can be directly executed with node path/to/myQuery.sql.js obtaining valid SQL to be executed in a sql interpreter

      • This way you can avoid the mess of copying forth and back the query and replace parameter specification (or values) each time from query testing environments to application code: Simply use the same file.

      • Note: I used PostgreSQL syntax for variable names. But with other databases, if different, it's pretty simple to adapt.

    • More than that: with a few more tweaks (see BONUS section), you can turn it in a viable console testing tool and:

      • Generate yet parametized sql by executing something like node myQueryFile.sql.js parameter1 parameter2 [...].
      • ...or directly execute it by piping to your database console. Ex: node myQueryFile.sql.js some_parameter | psql -U myUser -h db_host db_name.
    • Even more: You also can tweak the query making it to behave slightly different when executed from console (see BONUS 2 section) avoiding to waste space displaying large but no meaningful data while keeping it when the query is read by the application that needs it.

      • And, of course: you can pipe it again to less -S to avoid line wrapping and be able to easily explore data by scrolling it both in horizontal and vertical directions.

    Example:

    (
        echo "\set someVar 3"
        echo "\set someOtherVar 'foo'"
        node path/to/myQuery.sql.js
    ) | psql dbName
    

    NOTES:

    • '@@sql@@' and '@@/sql@@' (or similar) labels are fully optional, but very useful for proper syntax highlighting, at least in Vim.

    • This extra-plumbing is no more necessary (see BONUS section).

    In fact, I actually doesn't write below (...) | psql... code directly to console but simply (in a vim buffer):

    echo "\set someVar 3"
    echo "\set someOtherVar 'foo'"
    node path/to/myQuery.sql.js
    

    ...as many times as test conditions I want to test and execute them by visually selecting desired block and typing :!bash | psql ...

    BONUS: (edit)

    I ended up using this approach in many projects with just a simple modification that consist in changing last row(s):

    module.parent || console.log(module.exports);
    // (or simply "p || console.log(module.exports);")
    

    ...by:

    p || console.log(
    `
    \\set someVar '''${process.argv[2]}'''
    \\set someOtherVar '''${process.argv[3]}'''
    `
    + module.exports
    );
    

    This way I can generate yet parametized queries from command line just by passing parameters normally as position arguments. Example:

    myUser@myHost:~$ node myQuery.sql.js foo bar
    
    \set someVar '''foo'''
    \set someOtherVar '''bar'''
    
    --@@sql@@
        select foo from bar
        where x = ${someVar} and y = ${someOtherVar}
    --@@/sql@@
    

    ...and, better than that: I can pipe it to postgres (or any other database) console just like this:

    myUser@myHost:~$ node myQuery.sql.js foo bar | psql -h dbHost -u dbUser dbName
     foo  
    ------
      100
      200
      300
    (3 rows)
    

    This approach make it much more easy to test multiple values because you can simply use command line history to recover previous commands and just edit whatever you want.

    BONUS 2:

    Two few more tricks:

    1. Sometimes we need to retrieve some columns with binary and/or large data that make it difficult to read from console and, in fact, we probaby even don't need to see them at all while testing the query.

    In this cases we can take advantadge of the p variable to alter the output of the query and shorten, format more properly, or simply remove that column from the projection.

    Examples:

    • Format: ${p ? jsonb_column : "jsonb_pretty("+jsonb_column+")"},

    • Shorten: ${p ? long_text : "substring("+long_text+")"},

    • Remove: ${p ? binary_data + "," : "" (notice that, in this case, I moved the comma inside the exprssion due to be able to avoid it in console version.

    2. Not a trick in fact but just a reminder: We all know that to deal with large output in the console, we only need to pipe it to less command.

    But, at least me, often forgive that, when ouput is table-aligned and too wide to fit in our terminal, there is the -S modifier to instruct less not to wrap and instead let us scroll text also in horizontal direction to explore the data.

    Here full version of the original snipped with this change applied:

    // myQuery.sql.js
    "use strict";
    
    var p = module.parent;
    var someVar = p ? '$1' : ':someVar'; // Comments if needed...
    var someOtherVar = p ? '$2' : ':someOtherVar';
    
    module.exports = `
    --@@sql@@
        select
            foo
            , bar
            , ${p ? baz : "jsonb_pretty("+baz+")"}
            ${p ? ", " + long_hash : ""}
        from bar
        where x = ${someVar} and y = ${someOtherVar}
    --@@/sql@@
    `;
    
    p || console.log(
    `
    \\set someVar '''${process.argv[2]}'''
    \\set someOtherVar '''${process.argv[3]}'''
    `
    + module.exports
    );
    

    FINAL EDIT:

    I have been evolving a lot more this concept until it became too wide to be strictly manually handled approach.

    Finally, taking advantage of the great ES6+ Tagged Templates i implemented a much simpler library driven approach.

    So, in case anyone could be interested in it, here it is: SQLTT

提交回复
热议问题