From 464570e2632ed6a9ea94efaee9da3dccd37bf7cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:24:31 +0000 Subject: [PATCH 1/2] Initial plan From 6e1e482d93cbb0dc480406d27dd76c667360bac1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:34:01 +0000 Subject: [PATCH 2/2] Reorganize docs, add AGENTS.md and SKILLS.md - Split 1831-line README into focused docs/ files - README.md now serves as a clean 138-line entry point - docs/EXAMPLES.md: annotated code examples - docs/USAGE.md: installation and usage guide - docs/ENGINES.md: engine system reference - docs/TEMPLATE_FORMAT.md: template source syntax - docs/API.md: complete API reference - docs/ADVANCED.md: hooks, mutations, CTEs, SQL alternatives - docs/ABOUT.md: project background and roadmap - AGENTS.md: guide for AI coding agents working on the repo - SKILLS.md: using SQLTT as a GitHub Copilot Skill --- AGENTS.md | 151 ++++ README.md | 1841 ++------------------------------------- SKILLS.md | 172 ++++ docs/ABOUT.md | 95 ++ docs/ADVANCED.md | 202 +++++ docs/API.md | 324 +++++++ docs/ENGINES.md | 106 +++ docs/EXAMPLES.md | 242 +++++ docs/TEMPLATE_FORMAT.md | 146 ++++ docs/USAGE.md | 189 ++++ 10 files changed, 1701 insertions(+), 1767 deletions(-) create mode 100644 AGENTS.md create mode 100644 SKILLS.md create mode 100644 docs/ABOUT.md create mode 100644 docs/ADVANCED.md create mode 100644 docs/API.md create mode 100644 docs/ENGINES.md create mode 100644 docs/EXAMPLES.md create mode 100644 docs/TEMPLATE_FORMAT.md create mode 100644 docs/USAGE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..bc22db8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,151 @@ +# AGENTS.md — Guide for AI Coding Agents + +This file helps AI coding agents (GitHub Copilot, Claude, GPT, etc.) understand +the SQLTT repository and work with it effectively. + +--- + +## What is SQLTT? + +**SQLTT** (*SQL Tagged Templates*) is a Node.js library for managing SQL queries +using ES6+ Tagged Template Literals. It: + +- Keeps SQL readable and maintainable in JavaScript projects. +- Renders the same query for multiple database engines (PostgreSQL, Oracle, …) + from a single template source. +- Generates both application-driver SQL and database-CLI SQL from the same template. +- Provides a rich interpolation API for argument handling, query composition, and more. + +It is **not an ORM** — it does not generate SQL from JavaScript objects. It is +a template engine for hand-written SQL. + +--- + +## Repository Layout + +``` +sqltt/ +├── index.js # Public entry point (re-exports lib/) +├── lib/ +│ ├── definitions.js # Shared constants and type definitions +│ ├── engines.js # Database engine implementations (add new engines here) +│ ├── helpers.js # Internal utility functions +│ ├── interpolation.js # Core tagged-template interpolation logic +│ ├── compiler_base.js # Base SQL compiler +│ ├── compiler_common.js # Shared compiler helpers +│ ├── compiler_sql.js # SQL-specific compiler +│ ├── compiler_args.js # Arguments compiler +│ ├── cli_mode.js # CLI execution handling +│ ├── privateMethods.js # Private template methods +│ ├── tplAPI.js # Template API (.sql(), .args(), .concat(), …) +│ └── staticAPI.js # Static API (.publish(), …) +├── test/ +│ ├── test.js # Main test runner +│ └── unit_tests.js # Unit tests (lib/engines.js, etc.) +├── examples/ # Example template files (.sql.js) +├── docs/ # Detailed documentation (see below) +├── README.md # Quick-start overview +├── AGENTS.md # This file +└── SKILLS.md # How to use SQLTT as a Copilot Skill +``` + +--- + +## Documentation Structure + +| File | Contents | +|:-----|:---------| +| [`README.md`](README.md) | Overview and quick-start | +| [`docs/EXAMPLES.md`](docs/EXAMPLES.md) | Annotated code examples | +| [`docs/USAGE.md`](docs/USAGE.md) | Installation, setup, and usage guide | +| [`docs/ENGINES.md`](docs/ENGINES.md) | Engine system and flavour selection | +| [`docs/TEMPLATE_FORMAT.md`](docs/TEMPLATE_FORMAT.md) | Template source syntax reference | +| [`docs/API.md`](docs/API.md) | Complete API reference (Template API, Tag API, Static Methods) | +| [`docs/ADVANCED.md`](docs/ADVANCED.md) | Hooks, SQL alternatives, mutations, CTEs | +| [`docs/ABOUT.md`](docs/ABOUT.md) | Project background and roadmap | + +--- + +## Running the Tests + +```sh +npm install +npm test +``` + +Tests use [Mocha](https://mochajs.org/). Test files live in `test/`. + +--- + +## Adding a New Engine + +1. Open `lib/engines.js`. +2. Follow the pattern of an existing engine (e.g. `postgresql`). +3. Each engine object specifies how to render positional parameters, variable + declarations for CLI mode, and any other engine-specific transforms. +4. Add your engine to the exported map with a unique key. +5. Add a test in `test/unit_tests.js` if appropriate. +6. Document it in [`docs/ENGINES.md`](docs/ENGINES.md). + +--- + +## Making Code Changes + +Key rules to follow: + +- **Tests first** — run `npm test` after any change to `lib/` or `index.js`. +- **No new dependencies** — the library has minimal dependencies by design. + Propose adding one only if there is no reasonable alternative. +- **Engine parity** — if you change how arguments or keywords are rendered, + make sure the change works correctly across all existing engines. +- **Backwards compatibility** — this is a prerelease, but try not to break the + public API (`.sql()`, `.args()`, `.concat()`, `.options()`, `.publish()`). + +--- + +## Useful Grep Patterns + +```sh +# Find where engines are defined +grep -n "postgresql\|oracle" lib/engines.js + +# Find where positional params are rendered +grep -rn "param\|placeholder\|\$1\|:1" lib/ + +# Find all Tag API methods +grep -n "tag\.\|proto\." lib/tplAPI.js lib/interpolation.js + +# Find CLI handling +grep -rn "cli_mode\|publish" lib/staticAPI.js lib/cli_mode.js +``` + +--- + +## Using SQLTT in a Project (Quick Reference) + +```javascript +const sqltt = require("sqltt"); + +// Single template +const q = new sqltt($=>$` + select id, name + from users + where id = ${"userId"} +`); + +// Render SQL +const sql = q.sql("postgresql"); // "select id, name from users where id = $1" +const args = q.args({ userId: 42 }); // [42] + +// db.query(sql, args); +``` + +For full details see [`docs/USAGE.md`](docs/USAGE.md) and +[`docs/API.md`](docs/API.md). + +--- + +## Using SQLTT as a Copilot Skill + +See [`SKILLS.md`](SKILLS.md) for instructions on loading SQLTT as a GitHub +Copilot Skill to get in-editor query generation and review assistance. diff --git a/README.md b/README.md index 1269196..96aab72 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,64 @@ - ![SQLTT - SQL Tagged Templates](sqltt_logo.png "SQLTT - SQL Tagged Templates") - [![Known Vulnerabilities](https://snyk.io/test/npm/sqltt/badge.svg?style=flat-square)](https://snyk.io/test/npm/sqltt) +[![npm version](https://badge.fury.io/js/sqltt.svg)](https://www.npmjs.com/package/sqltt) ----------------------------------------------------- - - - -*SQL Tagged Templates* (sqltt) allows to easily manage SQL queries from -Javascript ([or even non Javascript](#using-from-non-javascript-languages)) -Projects taking advantadge of the [ES6+ Tagged -Templates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates) -feature. - -It is not and is not meant to be an ORM: It's just a template system intended -to keep queries readable and easy to maintain while inforcing reusability and -helping us to keep them organized. - -It also let us to handle syntax mismatchings between database engines in order -to only keep and maintain single version of each query even if it needs to be -executed on different RDBMS. - -([More…](#about)) - -> ☞ [About this prerelease…](#about-this-prerelease) +--- - +*SQL Tagged Templates* (**SQLTT**) lets you manage SQL queries from JavaScript +projects using [ES6+ Tagged Template Literals][tagged-templates]. -| 💡 [Examples](#examples) | 📖 [Usage Manual](#usage-manual) | 💼 **More...** | -|--------------------------------------------------------|-------------------------------------|-----------------------------------------| -| [Template Syntax](#template-example) | [Features](#features) | [ABOUT](#about-sqltt) | -| [Usage as CLI Tool](#executing-from-cli) | [Template Format](#template-format) | [Advanced Features](#advanced-features) | -| [Usage as Node Module](#using-from-nodejs-application) | [API Reference](#api-reference) | [TODO](#todo) | -| [Usage from non js languages](using-from-non-javascript-languages) | [Static Methods](#static-methods) | [Contributing](#contributing) | +It is **not an ORM**: it is a template engine that keeps queries readable and +maintainable while enforcing reusability — and it renders the same query for +multiple database engines from a single source. +> ☞ [About this prerelease & roadmap](docs/ABOUT.md) -Examples --------- +[tagged-templates]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates -Following are a few examples to better understand what *SQLTT* does and how -powerful it is just in a glance. +--- -> 📌 Unless stated otherwise, all following examples are for *PostgreSQL* -> engines. Either case, if engine is not specified, a "generic" one gets used -> (which nowadays is exactly the same as Postgres one). +## Documentation +| | | +|:--|:--| +| 💡 [Examples](docs/EXAMPLES.md) | Annotated examples: CLI, Node.js, multi-engine | +| 📖 [Usage Guide](docs/USAGE.md) | Installation, setup, writing and using templates | +| 🗂️ [Template Format](docs/TEMPLATE_FORMAT.md) | All template source properties | +| 🔧 [API Reference](docs/API.md) | Template API, Tag API, Static Methods | +| ⚙️ [Engines](docs/ENGINES.md) | Engine system and multi-database support | +| 🚀 [Advanced Features](docs/ADVANCED.md) | Hooks, mutations, CTEs, SQL alternatives | +| ℹ️ [About & Roadmap](docs/ABOUT.md) | Project background and upcoming features | +| 🤖 [AGENTS.md](AGENTS.md) | Guide for AI coding agents working on this repo | +| 🎯 [SKILLS.md](SKILLS.md) | Using SQLTT as a GitHub Copilot Skill | -### Template example +--- - +## Quick Start -The following example shows how a *SQLTT* template looks like and how we can -use that template to generate actual SQL suitable for multiple database engines -or even directly execute it through a *CLI* interpreter. +### Install +```sh +npm install --save sqltt +``` -**$ ``cat personnel.sql.js``** +### Write a Template File ```javascript +// personnel.sql.js const sqltt = require("sqltt"); -const commonFields = ["dptId", "name", "sex", "birth"]; +const commonFields = ["name", "sex", "birth"]; const tpl = {}; tpl.list = new sqltt(` - select id, dptName, name + select id, name, sex from personnel - join depts using(dptId) -`); - -tpl.listByDept = new sqltt($=>$` - ${tpl.list} ${$.REM("Same as ${$.include(tpl.list)}")} - where dptId = ${"dptId"} ${$.REM("Same as ${$.arg('dptId')}")} `); tpl.show = new sqltt($=>$` - select id, dptName, name, birth, ctime + select id, name, sex, birth, ctime from personnel - join depts using(dptId) where id = ${"id"} `); @@ -92,1740 +72,67 @@ tpl.update = new sqltt($=>$` where id = ${"id"} `); -sqltt.publish(module, tpl); // Export and make available from CLI -``` - - - - -#### Syntax highlighting - -As long as *SQLTT* template files are javascript files you would probably get -javascript syntax highlighting in your preferred editor by default. But you -would probably prefer SQL hilighting instead or, even better, both wherever -they apply. - -Most obvious solutions is to use .sql extension instead (or use a *modeline* -specifying different file type in editors that support it such as vim): - -**$ ``mv personnel.sql.js personnel.js.sql``** - -But this way you will loose javascript syntax highlighting in place. - -Better solution would be to keep default *javascritp* syntax highlighting and -change it to SQL just for the sections where it's needed. - -This is why in this documentation uses '.sql.js' extension for template files -instead of '.js.sql'. - -This can be esasily be done in vim, and probably in many other editors (if you -know it for any other, please send me a reference to include in this section). - - -#### Block-specific syntax highlighting in vim - -To enable *block-specific* syntax highlighting in vim see [Different syntax -highlighting within regions of a -file](http://vim.wikia.com/wiki/Different_syntax_highlighting_within_regions_of_a_file). - - -```javascript -tpl.someQuery = new sqltt( /* @@sql@@ */ $=>$` - -- Your query here -` /* @@/sql@@ */); -``` - -> 📌 You can find more complete examples in the [Examples directory of this GIT -> repository](https://github.com/bitifet/sqltt/tree/master/examples). - - -### Executing from cli - - - -This specific template example provide multiple queries in single file so, when -invoked from command line without arguments, it will ask us for a query -selection: - -**$ ``node personnel.sql.js``** - -```sh -Available queries: - ✓ list: (Undocumented) - ✓ listByDept: (Undocumented) - ✓ show: (Undocumented) - ✓ insert: (Undocumented) - ✓ update: (Undocumented) -``` - -> 📌 If single SQLTT template where published in that file, we would had got -> its SQL instead just like we are going to obtain right now by specifying it. - -Now we can obtain desired query just by specifying it as a parameter: - -**$ ``node personnel.sql.js list``** - -```sql - select id, dptId, dptName, name, sex - from personnel - join depts using(dptId) -``` - - -...and, of course, we can pipe it to our preferred database engine too: - -**$ ``node personnel.sql.js list | psql tiaDB``** - -```sh - id | dptid | dptname | name | sex -----+------------+----------------+-----------+----- - 1 | management | Management | Super | m - 3 | oper | Operations | Filemon | m - 2 | oper | Operations | Mortadelo | m - 4 | adm | Administration | Ofelia | f - 5 | i+d | I+D | Bacterio | m -(5 rows) -``` - -Other queries may require arguments, so we just provide them as additional -command line arguments: - -**$ ``node personnel.sql.js listByDept oper``** - -```sql -\set dptId '''oper''' - select id, dptId, dptName, name, sex - from personnel - join depts using(dptId) - where dptId = :dptId -``` - -...and, again, we can execute obtained sql too: - -**$ ``node personnel.sql.js listByDept oper | psql tiaDB``** - -```sh - id | dptid | dptname | name | sex -----+-------+------------+-----------+----- - 2 | oper | Operations | Mortadelo | m - 3 | oper | Operations | Filemon | m -(2 rows) -``` - - - - -#### Types and quoting - -In cli-mode we have no way to precisely determine which type is each argument -and hence neither which would be the correct quoting to use. - -To overcome it, following convention is used: - - * When it is enclosed by single or double quotes, it is a character-based type (string). - - Note that those quotes **need to avoid shell escapping**. I.E.: ``"'This - is a string'"``. - - * When unquoted: - - - 'null' (Case insensitive) stands for NULL. - - - 'true' (Case insensitive) stands for boolean true. - - - 'null' (Case insensitive) stands for boolean true. - - - Valid numeric value stands for Number. - - - Else case, it is supposed to be a string, even unquoted. - - -### Using from NodeJS application - - - -Since we export our templates as SQLTT instances we just need to require and -start using them. - -> 📌 In [previous example](#template-example) we *published* our templates -> through ``sqltt.publish(module, tpl);`` statement which, from our point of -> view now, is the exact same as exporting them through ``module.exports = -> tpl;`` except for the fact that this way it wouldn't have been [usable from -> CLI](#executing-from-cli) too like it had. - -Either if we exported/published single *SQLTT Template* or multiple ones, now -we are able to use them through multiple methods which we call them our -*[Template API](#template-api)*. - - -**Example:** - -```javascript -const personnelSQL = require('path/to/personnel.sql.js'); - -// Show rendered SQL of each query: -console.log (personnelSQL.list.sql()); -console.log (personnelSQL.listByDept.sql()); -console.log (personnelSQL.show.sql()); -console.log (personnelSQL.insert.sql()); -console.log (personnelSQL.update.sql()); -``` - - -> 📌 ``.sql()`` method also accepts an optional parameter to specify desired -> [engine flavour](#engine-flavours). If not specified, default one is used. - - -Another commonly used *[Template API](#template-api)* method is ``.args()`` -which let us to convert a *{key: value}* pairs object to a properly sorted -arguments array to feed our database query method. - - -**Example using [ppooled-pg](https://www.npmjs.com/package/ppooled-pg):** - -```javascript -// Insert new item -const personnelSQL = require('path/to/personnel.sql.js'); -const db = require('ppooled-pg')(/*connection data*/); -const newPerson = { - name: "Chorizez", - dptId: "robbers", - sex: "m", - // birth: "", // Unknown data will default to null - connection: { // Unused data will be ignored - ip: "10.0.1.25", - port: 80, - trusted: false, - } -}; -db.queryRows( - personnelSQL.insert.sql("postgresql") - , personnelSQL.insert.args(newPerson) -).then(rows=>console.log(rows); -``` - -> 📌 From version 0.3.0, [ppooled-pg natively supports for *SQL Tagged -> Templates*](https://www.npmjs.com/package/ppooled-pg#support-for-sql-tagged-templates) -> so we could simply have wrote: ``db.queryRows(personnelSQL.insert, -> newPerson)``. -> -> But this example was intended to work with most database libraries with -> minimum changes. - -See *[Template API](#template-api)* secton for more details of available -methods and their options. - - - - -### Using from non javascript languages - - - -Athough they're javascript, we can take advantadge of *SQLTT* templates even -for other language programs. - -Despite they won't be able directly use [Template API](#template-api) methods -such as [args()](#argsargdata), we are still able to use *SQLTT* templates to -keep our queries readable and easy to mantain. - -To use them from our non-javascript application, all we need to do is to -compile them using [SQLTT CLI capabilities](#executing-from-cli) with [*_nocli* -engines](#query-output-inspection): - - -**$ ``node --engine=postgresql_nocli personnel.sql.js listByDept oper``** - -```sql - select id, dptId, dptName, name, sex - from personnel - join depts using(dptId) - where dptId = $1 -``` - -This way we can build simple simple *compilation scripts* such as following -example: - -```sh -#!/bin/env sh -export SQL_ENGINE=postgresql_nocli - -node sqlsrc/personnel.sql.js list > sql/personnel.list.sql -node sqlsrc/personnel.sql.js listByDept > sql/personnel.listByDept.sql -node sqlsrc/personnel.sql.js show > sql/personnel.show.sql -node sqlsrc/personnel.sql.js insert > sql/personnel.insert.sql -node sqlsrc/personnel.sql.js update > sql/personnel.update.sql - -node sqlsrc/articles.sql.js list > sql/articles.list.sql -node sqlsrc/articles.sql.js find > sql/articles.find.sql -node sqlsrc/articles.sql.js show > sql/articles.show.sql -node sqlsrc/articles.sql.js insert > sql/articles.insert.sql -node sqlsrc/articles.sql.js update > sql/articles.update.sql - -# .... -``` - - - -USAGE MANUAL -============ - -Table of Contents ------------------ - - - -* [ABOUT SQLTT](#about-sqltt) - * [About this prerelease](#about-this-prerelease) - * [Release TODO](#release-todo) - * [Fix .concat(), .limit() and .wrap() through wrapping engines](#fix-concat-limit-and-wrap-through-wrapping-engines) - * [Implement commmnd-line modifiers](#implement-commmnd-line-modifiers) - * [Implement Mutable Queries](#implement-mutable-queries) - * [1. Implement .data(key) Tag API method](#1-implement-datakey-tag-api-method) - * [2. Implement *wrapStr* additional argument](#2-implement-wrapstr-additional-argument) - * [3. Implement .data() Template API method](#3-implement-data-template-api-method) - * [4. Implement .data() "presets"](#4-implement-data-presets) - * [5. Enhance CLI functionality with mutations](#5-enhance-cli-functionality-with-mutations) - * [5.1 Allow presets too](#51-allow-presets-too) - * [6. Allow multiple mutations](#6-allow-multiple-mutations) - * [7. Even more functionality](#7-even-more-functionality) - * [8. Update documentation](#8-update-documentation) - * [Implement CTE "dependency" system](#implement-cte-dependency-system) - * [DONE:](#done) - * [TO-DO:](#to-do) - * [Add .options() methods to .publish() exports](#add-options-methods-to-publish-exports) - * [Auto-name queries in multiquery files](#auto-name-queries-in-multiquery-files) -* [FEATURES](#features) -* [BASIC CONCEPTS](#basic-concepts) - * [Engines](#engines) - * [Currently supported engines](#currently-supported-engines) - * [Adding more Database Engines](#adding-more-database-engines) - * [Engine Flavours and Targets](#engine-flavours-and-targets) - * [SQL_ENGINE environment variable](#sql_engine-environment-variable) - * [--engine](#-engine) -* [SETUP AND USAGE](#setup-and-usage) - * [Package setup](#package-setup) - * [Syntax](#syntax) - * [Accepted Options:](#accepted-options) - * [Writing templates](#writing-templates) - * [Usage](#usage) - * [From application](#from-application) - * [From CLI](#from-cli) - * [Providing arguments](#providing-arguments) - * [Executing queries](#executing-queries) - * [Selecting Engine Flavour](#selecting-engine-flavour) - * [Query output inspection](#query-output-inspection) -* [TEMPLATE FORMAT](#template-format) - * [Name](#name) - * [Description](#description) - * [SQL Callback](#sql-callback) - * [Arguments declaration](#arguments-declaration) - * [Alternative SQL](#alternative-sql) - * [Default Engine](#default-engine) - * [Data](#data) - * [with](#with) -* [API REFERENCE](#api-reference) - * [Template API](#template-api) - * [sql(engFlavour)](#sqlengflavour) - * [args(argData)](#argsargdata) - * [concat(str)](#concatstr) - * [options(optsObject)](#optionsoptsobject) - * [Tag API](#tag-api) - * [arg()](#arg) - * [include()](#include) - * [keys(), values() and entries()](#keys-values-and-entries) - * [literal(str)](#literalstr) - * [data(str)](#datastr) - * [Static Methods](#static-methods) - * [publish(module, tpl)](#publishmodule-tpl) -* [Advanced Features](#advanced-features) - * [Hooks](#hooks) - * [SQL Alternatives](#sql-alternatives) - * [String Concatenation](#string-concatenation) -* [TODO](#todo) -* [Contributing](#contributing) - - - -ABOUT SQLTT ------------ - - - -SQL is a powerful language, but most databases come with their own variations -and nuances. - -Even for the same database engine, the syntax used in application libraries and -[CLI](https://en.wikipedia.org/wiki/Command-line_interface#Other_command-line_interfaces) -interpreters usually differ. At least for parametyzed queries. - -This often forces developers to modify their queries back and forth to test -them in database CLI or, even worst, when they need to support different -database engines. In which case they are most times forced to mantain -completely different versions of the same query for each supported database. - -ORM solutions solve that problem at the cost of generating suboptimal queries -and disallowing most powerful SQL and/or database-specific features. - -SQLTT allows us to maintain single version of each query while preserving the -whole power of actual SQL also providing many advanced features such as reusing -snipppets or whole queries and [much more](#features) fully embracing the -[DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) principle. - -> 💡 **Original Idea:** Original idea comes from [this StackOverflow -> answer](https://stackoverflow.com/a/41136912/4243912) which I have been using -> and progressively evolving until it had become too big to agglutinate all -> possible features I have been adding over time. - - - - - -### About this prerelease - - - -I started publishing prereleases because I've decided that next SQLTT version -will be 1.0.0 because it has so breaking changes to require increasing major -version number. - -I also fixed as a goal to publish it as a mature library with a complete -documentation and test suite. - -(Not really un-) fortunately, during that process, many exciting ideas such as -(.keys(), .values() and .entries(), "nocli", enhnanced operations, etc...) came -in to my head so I continuously postposed 1.0.0 release. - -Meanwhile I started to need already implemented features in a real project -(from which in fact SQLTT comes from) so I started to publish prereleases in -NPM. - -I'm working in finishing tests and documentation every time I can. But there -still new Ideas that I think they are a "must to" because of the power they -will conver to SQLTT as a tool. - -For this reason, I added this section and the following [Release -TODO](release-todo) to track things that left to be implemented before final -SQLTT-1.0.0 - -I hope it won't continue growing much more and I could deliver final -SQLTT-1.0.0 soon. - - - - -### Release TODO - -#### Fix .concat(), .limit() and .wrap() through wrapping engines - -``.concat()``, ``.limit()`` and ``.wrap()`` all wraps the output of original -.sql() method call. - -This is wrong when engines, such as oracle, provide their own wrapping -(resulting in an inverse wrapping order). - -I.E. (from CLI) ``$ node myTpl.sql.js someQuery --oracle --limit 10`` would now -output ``select foo from bar; LIMIT 10`` instead of ``select foo from bar LIMIT -10;`` - - -#### Implement commmnd-line modifiers - - * (DONE) Implement --engine=eng_name to override SQL_ENGINE env. var. - - "--" shorthands implementd too (i.e.: "--postgresql"). - - * Implement --all to render all defined templates. - - Precede each one with a comment showing its name. - - Implement it by multiple inclusion, so that arguments will be declared - globally on top. - -#### Implement Mutable Queries - -##### 1. Implement .data(key) Tag API method - - (DONE) - -It will give access to data declared in *data* key from template source and -will be able to be used from other methods such as .entries() to access data -defined inside the template. - -**I.e.:** - -```javascript -tpl.getUserData = new sqltt({ - data: { - columns: ["id", "name", "sex", "birth", "ctime"], - filters: ["name"], - }, - sql: $=>$` - select ${$.arg($.data("columns"))} - from users - ${$.entries($.data("filters"), "and", "where %")} - // For the third .entries() parameter see next TODO - `, -}); +sqltt.publish(module, tpl); ``` -> ...Additionally, methods that currently doesn't accept string as its first -> argument, would be modified to automatically call .data() with it when string -> were provided. This way, previous call to *.entries()* could be simplified as -> ``$.entries("filters", "and", "where %")``. - - - -##### 2. Implement *wrapStr* additional argument - - (DONE) - -Implement *wrapStr* additional argument at least for .keys(), .values() and -.entries(). - -It will provide a simple wrapping string (see previous example) that will be -applied only if that method renders something. - -This way, if in previous example, *filters* were had been an empty array even -the *where* clause (provided through this wrapping argument) weren't got -rendered so, executing that query, all rows would be returned. - - - -##### 3. Implement .data() Template API method - - (DONE) - -After previous step, we can implement new Template API method with the same -name (*.data(patch)*). - -This method will return a new instance of the template with its template *data* -property patched. - -SYNTAX: ``myTpl.data(dataPatch)`` - -Everey newData entry will replace existing one. - -To remove existing entries, they can be set to null, or undefined. - -This third piece will allow us to "mutate" queries by specifying different -column list to show or filters to apply (required arguments would change in -this case). - - - -##### 4. Implement .data() "presets" - - (DONE) - -Modify .data() Template API method so that if provided dataPatch is an array or -a string of comma separated keys instead of an object, it will check for a -"presets" prooperty in template source and then for every specified key. - -Next, those objects will all be applied as data patches. - - - -##### 5. Enhance CLI functionality with mutations - - (DONE) - -Extend CLI controller so that, in multitemplate case, when addressing a temlate -(such as 'list') we could add a literal *data* specification in parentheses -and, in that case, template will get mutated according that. - -I.e. following our [initial example](#template-example), 'list' and listByDept' -queries would be merged in single one: - +### Use from a Node.js Application ```javascript -tpl.list = new sqltt({ - data: { - columns: ["id", "dptName", "name"], - // filters: [], // Non declared data sets defaults to empty array. - }, - sql: $=>` - select ${$.arg($.data("columns"))} - from personnel - join depts using(dptId) - ${$.entries($.data("filters"), "and", "where %")} - `, -}); -``` - -Now, to get previously named *listByDept* query from CLI, we just need to run: - -**$ ``node personnel.sql.js 'list({filters: ["dptId"]})' | psql tiaDB``** - -```sh - id | dptid | dptname | name | sex -----+-------+------------+-----------+----- - 2 | oper | Operations | Mortadelo | m - 3 | oper | Operations | Filemon | m -(2 rows) -``` +const personnel = require('./personnel.sql.js'); - +const sql = personnel.show.sql('postgresql'); +// "select id, name, sex, birth, ctime from personnel where id = $1" -###### 5.1 Allow presets too +const args = personnel.show.args({ id: 42 }); +// [42] - (DONE) - -Consider now this slight modification of prevous example. - -```javascript -tpl.list = new sqltt({ - data: { - columns: ["id", "dptid", "dptname", "name", "sex"], - }, - presets: { - detailed: {columns: ["id", "dptId", "dptName", "name", "sex", "ctime"]}, - bySection: {filters: ["dptId"]}, - }, - sql: $=>` - select ${$.arg($.data("columns"))} - from personnel - join depts using(dptId) - ${$.entries($.data("filters"), "and", "where %")} - `, -}); +// db.query(sql, args); ``` -Presets should be allowed from cli-mode too just invoking them by its name, so -now we could simply had done: - -**$ ``node personnel.sql.js 'list(bySection)' | psql tiaDB``** +### Use from the CLI ```sh - id | dptid | dptname | name | sex -----+-------+------------+-----------+----- - 2 | oper | Operations | Mortadelo | m - 3 | oper | Operations | Filemon | m -(2 rows) -``` - -> 📌 Since `.data()` accepts multiple presets, we could also pick more than one -> preset: -> -> **$ ``node personnel.sql.js 'list(bySection,detailed)' | psql tiaDB``** -> -> ```sh -> id | name | sex | dptName | birth | ctime -> ----+-----------+-----+----------------+------------+---------------------------- -> 1 | Mortadelo | m | Operations | 1969-03-10 | 2019-05-31 10:58:09.346467 -> 2 | Filemon | m | Operations | 1965-08-15 | 2019-05-31 10:58:46.291629 -> ``` - - - -##### 6. Allow multiple mutations - - - - * Modify ``.data()`` Template API method so that it accept any number of - arguments. - - That arguments could be both preset names or data patches to apply. - - They will be applied in order. - - ...and every key will be MERGED (concatenated in case of Arrays) instead of replaces. - - Keys with null values will be resetted: - - Current effect of ``.data({foo: {bar: "baz"}})``. - - ...will be ``.data({foo: null}, {foo: {bar: "baz"}})``. - - ...otherwise previous and non overwritten *foo* data would be preserved. - - A global null value (``.data(null [, ...])``) will fully reset whole data object. - - * Modify cli-mode mutation specification in order to allow to combine preset - names with data patches in the same way. - - - -##### 7. Even more functionality - - - -7.1 Rename .data() method to .mutation() - -7.2 Implement a new (and simpler) .data() method which just replaces whole - *data* property (semi-backward compatibility). +# List available queries +node personnel.sql.js -7.3 Enhance .mutation method in order to allow presets to be callbacks - returning the actual mutation. - - Those callbackw would accept parameters too, that could be passed ussing - function-like syntax too. - - They will also be bounded to the tag function so Tag API methods will be - available though `this`. +# Render a specific query +node personnel.sql.js show -**🗂️ Example:** +# Render with arguments +node personnel.sql.js show 42 -```javascript -tpl.list = new sqltt({ - presets: { - page: function(num, itemsPerPage = 10) { - const $ = this; // Tag API - if (num === undefined) return { // Render them as parameters. - offset: $.arg("offset"), - limit: $.arg("limit"), - }; - if (num === null) return {}; // No offset or limit rendered at all. - // Use specified literals otherwise: - return { - offset: num * itemsPerPage, - limit: itemsPerPage, - }; - }, - }, - sql: $=>` - select * from personnel - ${$.keys($.data("offset"), $.default, "order by %")} - ${$.keys($.data("limit"), $.default, "limit %")} - - `, -}); -``` - -```javascript -myQuery.sql(); - // select * from personnel; -myQuery.mutation('page(null)').sql(); - // select * from personnel; -myQuery.mutation('page(20)').sql(); - // select * from personnel offset 200 limit 10; -myQuery.mutation('page(3, 50)').sql(); - // select * from personnel offset 150 limit 50; -myQuery.mutation('page(null)').sql(); - // select * from personnel; -myQuery.mutation('page()').sql(); - // select * from personnel offset $1 limit $2; +# Pipe directly to the database +node personnel.sql.js list | psql mydb ``` - - -##### 8. Update documentation - - - -Update documentation with that functionalities. - -Remember to consider examples for GraphQL APIs implementations. - - - -#### Implement CTE "dependency" system - - (ALMOST DONE) - -##### DONE: - - * Added new optional attribute 'WITH' to the template soure specification for - Common Table Expressions ("with" clause). - - * It accepts a {key: value} object of accepting same values that accepts - $.include() method. - - * They are considered as CTE's to be prepended to the actual query. - - In case of {key: value} object, each key will be used as CTE alias. - - Otherwise, *alias* attribute will be mandatory for resolved SQLTT instance. - - * They're processed recursively. That is: In case a CTE have its own CTE's, - they will be flattened to the parent query CTE block. - - In case of a CTE having a CTE with the same alias as already added CTE, - it is checked to be the same object and added single time. - - ...otherwise (same alias but not same object) an error is thrown. - -##### TO-DO: - - * Also accept any of the following (template 'name' attribute would be - mandatory too in this case): - - An array of the same kind of values. - - Single item of the same type. - - * CTEs from an $.include()'d query won't be flattened (except internally to - its own subctes) so aliases won't also collide. - - Nowadays $.include()ing queryes with CTE is not suported and throws an - error. - - - -#### Add .options() methods to .publish() exports - - - * Not enumerable. - * Only if not "options" query exported. - * It will let to globally alter default options. - (Not strictly necessary for 1.0.0, but maybe...) - - -#### Auto-name queries in multiquery files - -Modify publish() static method so that unnamed templates get automatically -named by its key in the object being published. - - - -FEATURES --------- - - - * Very simple, readable and non-intrusive [template - format](#template-format). - - * Don't Repeat Yourself (DRY): - - Render SQL [for your application](#from-application) properly - formatted for one or more database engines. Ex.: - - PostgreSQL: ``myTpl.sql('postgresql') // select [...] where baz = $1`` - - Oracle: ``myTpl.sql('oracle')' // select [...] where baz = :1`` - - ... - - Generate [database-specific CLI versions](#executing-queries) too. - - PostgreSQL: ``myTpl.sql('postgresql_cli') // select [...] where baz = :baz`` - - Oracle: ``myTpl.sql('oracle_cli') // select [...] where BAZ = '&baz'`` - - Auto: ``myTpl.sql('cli') // Use 'default_cli' unless SQL_ENGINE env var - defined`` or --engine= modifier used. - - Or even simpler: Direct [call from command line](#executing-from-cli) - if ['.publish()' method used](#publishmodule-tpl). - - ...all with **single SQL template source**. - - * Easy placeholders hanling: - - Readable strings as argument placeholders instead of ``$1``, ``$2``, - etc... (postgresql) or ``:1``, ``:2``, etc... (oracle), for example. - - Numeration can be (even partially) explicitly specified by enumerating - them in the ``args`` template property or infered by its apparition order - Non specified arguments are automatically fulfilled in query appearing - order. - - * Arguments generator helper: - - Generates properly sorted arguments array from a *{key: value,...}* object. - - Missing keys defaults to *null* and unused ones are silently ignored. - - Ex.: ``myTpl.args({foo: "fooVal", bar: "barVal"})``. - - * Direct execution: standard output can obviously redirected to any database - sql interpreter. - - Ex.: ``node myTplLib someQuery value1 "second value" | psql myDb`` - - * Query nesting: If, instead of a regular string, another *sqltt* instance is - interpolated, it will be correctly rendered in place and even its argument - declarations will be conveniently assumed in the right order and without - duplications. - - Ex.: ``$=>$`${listQuery} and typeid = ${"tid"}` ``. - - * Advanced Interpolation Api: When ``$=>$`...` `` form is used, the tag - function ("$" argument) comes with a bunch of methods providing more advanced - functionalities. - - In fact, ``${"someArg"}`` is, in fact, a shorthand for ``${$.arg("someArg"})}``. - - ...and ``${someSubtemplate}`` is the same as ``${$.include(someSubtemplate)}``. - - But they can take more arguments. Ex.: ``${$.include(someSubtemplate, - {arg1: "fixedValue"})}`` - - And there are a few more: - - ``$.literal()`` - - ``$.keys()`` and ``$.values()``. - - Ex.: ``insert into foo (${$.keys(someObj)}) values (${$.values(someObj)})`` - - ``$.entries()``. - - Ex.: ``update foo set ${$.entries(someObj)} where ...`` - - And more comming (``$.if(cnd, thenCase, elseCase)``, ...). - - * Debugging capabilities: A *debug* option can be enabled for single query or - whole *published* repository, those queries will log supplied arguments - every time *.arg()* method is called. - - This allows to cherry-pick debug information only for specific queries. - - Special "verbose" and "cli" values can be specified to show whole - provided (unparsed) argoments or to get - [kcli-suitable](#executing-from-cli) string. - - * Query Caché: - - SQL for every database are generated and cached the first time they're - required and then always consumed from caché. - - * SQL [Syntax Highlighting](#syntax-highlighting). - - - - -BASIC CONCEPTS --------------- - -### Engines - - - -*SQLTT* is database agnostic in the sense that it only provide a templating -layer and you are responsible to write SQL suitable for your specific -database(s) engine(s). - -But, at the same time, it provide you the tools to support multiple SQL syntax -variations with single codebase. - -So, depending on targetted database or even if we are generating SQL for an -application library or to be executed in a CLI interpreter, there could be -subtle syntax differences *SQLTT* must care on. - -The most obvious one is the way positional parameters must be specified in an -SQL string. Say: - - * ``$1``, ``$2``, ``$3``, ... (Postgresql) - * ``:1``, ``:2``, ``:3``, ... (Oracle) - * Etc... - -Or even by variable name, at least in many database CLIs: - - * ``:var1``, ``:var2``, ``:var3``, ... (Postgresql) - * ``'&var1'``, ``'&var2'``, ``'&var3'``, ... (Oracle) - * Etc... - -To handle these specific differences between targetted databases *SQLTT* uses -small specialyzed libraries called *engines*. - - - -#### Currently supported engines - - - -Currently supported engines by SQLTT are: - -| Name | Description | -|:-------------------|:----------------------------------------------------| -| ``default`` | Generic standard (TODO: ANSI compilant) SQL. | -| ``default_cli`` | Generic standard SQL suitable for CLI interpreters. | -| ``postgresql`` | PostgreSQL-specific SQL. | -| ``postgresql_cli`` | PostgreSQL-specific SQL for its CLI (pgsql) client. | -| ``oracle`` | Oracle-specific SQL. | -| ``oracle_cli`` | Oracle-specific SQL for its CLI (sqlplus) client. | - - - -#### Adding more Database Engines - - - -If you are interested in adding more engines or improving existing ones, check -``lib/engines.js`` file (They're too easy to implement) and please, feel free to -send me patches to include your improvements. - - - -### Engine Flavours and Targets - - - -As you could see from previous table, we have two engines for each specific -database flavour (PostgreSQL, Oracle, etc..): one targetting SQL for specific -database application libraries and the other, suffixed by *_cli*, for database -CLI interpreters. - -Sometimes we will only be interested in addressing desired flavour or desired -target. - -For example, when we call the ``.sql()`` method, we are supposed to expect SQL -for a database library. Not for CLI usage. So specifying 'postgresql' stands -for 'posgresql' engine: not 'postgresql_cli'. - -On the other hand, when we want to [use it from CLI](from-cli) we may be -interested in only select the cli-specific target but allowing to change the -actual database flavour. - - - -#### SQL_ENGINE environment variable - - - -To do so we can simply specify 'cli'. This way 'default_cli' will be addressed -by default, but it can be overridden by 'SQL_ENGINE' environment variable -(only if exactly 'cli' is specified). - -On the other hand, even when 'cli' or *someFlavour_cli* is specified, we can -set *SQL_ENGINE* to 'nocli' or *someFlavour_nocli* in order to override CLI -engine of selected database flavour. - -This can be useful if we only want to visually inspect how our query will be -served to our application through `.sql()` (or `.sql(flavour)`) method. - - - -#### --engine - -Additionally, in cli-mode, `--engine=` or simply its shorhands -`--` can be used to override *SQL_ENGINE* environment variable or -simply as a more handy way to select it. - - -SETUP AND USAGE ---------------- - -### Package setup - - - -Install *sqltt* executing ``npm install --save sqltt`` within your -project directory. - - - -### Syntax - - - -To load *SQLTT*: - -```javascript -const sqltt = require("sqltt"); -``` - -To create single *SQLTT* template: - -```javascript -const q = new sqltt(source, options); -``` - -**Where:** - - * ``source``: Defines the template and possibly other parameters (see - [template parts:](#template-format) below). - - * ``options``: Optional *options* object to specify a few behavioral - modifiers. - - - -#### Accepted Options: - - - - * **default_engine:** *(Optional)* Change the default rendering engine for - that template. That is: the engine that will be used to render SQL when it - is not explicitly specified in ``.sql()`` method call. - - * **check_arguments** (default: *true*): Allows to avoid template's *args* - validation checks (they will be auto-corrected instead of throwing an - error). - - * **debug** (default: *false*): When true, every call to .args() method of - this query will be logged. Valid values: - - *verbose*: Will log original arguments object. - - *cli*: Generate output suitable for cli usage. - - *true* (Any other true value): Array of the actually provided arguments. - - - - -### Writing templates - - - -Every template file may contain single or multiple *SQLTT* templates and may -export them either in its source form as already constructed *SQLTT* instances. - -But preferred way is as follows: - - -**For single template:** - -```javascript -const sqltt = require("sqltt"); -const tpl = new sqltt( - /* Template source in any valid format */ -); -sqltt.publish(module, tpl); -``` - -**For multiple templates:** - -```javascript -const sqltt = require("sqltt"); -const tpl = { - aQuery: new sqltt( /* Template source... */), - anotherQuery: new sqltt( /* Template source... */), - /* ... */ -}; -sqltt.publish(module, tpl); -``` - -See [Template Format](#template-format) below to learn about the syntax of -template sources. - - -### Usage - - - -The final ``sqltt.publish(module, tpl)`` statement in [previous -examples](#writing-templates) replaces classic ``module.exports = tpl`` and is -almost equivalent to: - -```javascript -module.exports = tpl; // Exports template. -module.parent || console.log( // Allow CLI usage. - tpl.sql('cli') -); -``` - -...except for that it also renders properly formattend "set/define" (depending -on database engine) arguments form commandline arguments. - - -> 👉 In fact it's slightly more complicated in order to properly handle -> multiple-template files too as well as other slight nuances. - -This allows us to use our constructed sqltt instances: - -1. [From application](#from-application): As a module from NodeJS application. -2. [From CLI](#from-cli): As a command line tool to get - *whatever_our_database_cli suitable* rendered SQL. - - - -#### From application - - -**Single Template Example:** - -```javascript -const myQuery = require('path/to/myTemplate.sql.js'); -const sql = myQuery.sql('postgresql'); -const args = myQuery.args({ - arg1: "val1", - arg2: "val2", - /* ... */ -}); - -// myDb.query(sql, args); -``` - -> 📌 The ``'postgresql'`` argument in ``myQuery.sql('postgresql')`` statement -> tells *SQLTT* to render *PostgreSQL* flavoured SQL syntax. -> -> Nowadays ``'postgresql'`` and ``'default'`` engine flavours works just the -> same. But other databases may have some nuances such as Oracle's way to specify -> arguments as ``:1``, ``:2``... instead of ``$1``, ``$2``... -> -> Additionally, templates can specify a ``default_engine`` property to override -> the default one. - - -**Multiple Template Example:** - -```javascript -const myQueries = require('path/to/myTemplate.sql.js'); -const userList_sql = myQueries.userList.sql('postgresql'); -const userProfile_sql = myQueies.userProfile.sql('postgresql'); - -const userProfile_args = myQueries.userProfile.args({ - userId: "someId", - /* ... */ -}); - -// myDb.query(userList_sql, []); -// myDb.query(userProfile_sql, userProfile_args); - -``` - - -#### From CLI - - -From command line we just need to execute our template through *node*: - -```sh -node path/to/myTemplate.sql.js -``` - -If it is a single template file it will directly render its SQL. - -Otherwise, and if no arguments provided, it will output a list of available -queries. - -**Example:** - -```sh -user@host:~/examples$ node personnel.sql.js -Available queries: list, listByDept, show, insert, update -``` - -...then we just need to pick for the desired query to render: - -```sh -user@host:~/examples$ node personnel.sql.js list - -select * from personnel; - -``` - - -##### Providing arguments - - -If our query requires arguments, we can feed it by simply adding them to the -command line: - -**Example:** - -```sh -user@host:~/examples$ node personnel.sql.js show 23 - -\set user_id '''23''' - select * - from personnel - where id = :id - -``` - -> 📌 From command line, when an argument is numeric, we can't tell whether it -> is intended to be an actual number type or string. -> -> For this reason all arguments are quoted unconditionally given that most -> database engines will automatically cast them as numbers when needed. - - - -##### Executing queries - - - -If we want to directly execute the query instead, we just need to pipe it to -our preferred database CLI interpreter. - -**Example:** - -```sh -user@host:~/examples$ node personnel.sql.js list | psql tiaDB - - id | name | sex | dptName | birth | ctime -----+-----------+-----+----------------+------------+---------------------------- - 1 | Mortadelo | m | Operations | 1969-03-10 | 2019-05-31 10:58:09.346467 - 2 | Filemon | m | Operations | 1965-08-15 | 2019-05-31 10:58:46.291629 - 3 | Ofelia | f | Administration | 1972-08-29 | 2019-05-31 11:05:16.594719 - 4 | Bacterio | m | I+D | 1965-08-15 | 2019-05-31 11:05:35.807663 -(...) -``` - - -##### Selecting Engine Flavour - - - -To render SQL from CLI, *default_cli* engine is selected by default except if -[``default_engine`` option](#optionsoptsobject) is set. For example, for -``temlate_engine: "postgresql"``, *postgresqsl_cli* will be picked for instead. - -On the other hand, in case we want to specifically pick for given database -engine flavour when we are going to generate SQL from *CLI*, we can set the -*SQL_ENGINE* environment variable in our shell either by: - - a) Exporting it (Ex.: ``export SQL_ENGINE=postgresql``). - - b) Setting just for single execution (Ex.: ``SQL_ENGINE=oracle node - myTpl.sql.js ...``). - -Lastly, *SQL_ENGINE* can be overridden by `--engine=` modifier or any -of its per-engine shorthands (`--`) such as `--postgresql`, -`--oracle_noci`, etc... - - - - -##### Query output inspection - -TODO: (``nocli``, ``*_nocli``)... - - - -TEMPLATE FORMAT ---------------- - - - -SQLTT templates consist in a JSON object with one or more of the following keys: - - * **name:** *(Optional)* - - * **description:** *(Optional)* - - * **sql:** *(Mandatory)* a SQL string or a [SQL Callback](#sql-callback). - Using a simple string provides a leaner way to define SQL string. But no - interpolated arguments are possible in this case. - - * **args:** *(Optional)* An array of strings declaring argument names and the - order in which they must be numbered. If ommitted or incomplete, the rest - of arguments will be appended in appearing order. - - * **alias:** *(Optional)* Provide an alias name to be used in case of de whole - query being included beside others through ``$.include([subq1, ...])``. - - * **altsql:** *(Optional)* One of the main goals of **SQLTT** is not having - to mantain multiple versions of the same query for different databases. But - when there is no other option, *altsql* let us to provide alternatives for - specific database engines. Ex.: `` altsql: { oracle: /* Oracle-specific sql - string or cbk */} ``. - - * **data:** (UNIMPLEMENTED) - - * **with:** (UNIMPLEMENTED) - - -**Examples:** - - * Arguments in given order: ``{args: ["baz"], sql: $=>$`select foo from bar where baz = ${"baz"}`} ``. - * Arguments in appearence order: ``$=>$`select foo from bar where baz = ${"baz"}` ``. - * Simple string: ``"select foo from bar"`` (no argumments in this case) - - - - -### Name - -An optional name to identyfy the query. - -To be used as default alias when query is included or used as CTE (with) for -another and also for documentation and debug logging purposes. - - -### Description - -An optional description. - - -### SQL Callback - - - -The *SQL Callback* receives single parameter (named `$`, even we can name it -whatever we like). - -This parameter is expected to receive a *tag function* and the whole callback -is expected to return an [ES6+ Tagged Template -Literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates) -generating an SQL statement. - -**Example:** - -```javascript -$=>$` - select foo - from bar - where baz = ${"value"} -``` - -**NOTES:** - - * *Arrow function* syntax is used too in order to minimize verbosity. - - * In ``${"value"}``, the ``$`` sign is part of *ES6+ Tagged Templates* - interpolation syntax (``${...}``). Not the tag function. - - That is: ``X=>X`select ... = ${"value"}`` could had been used instead. - - * The ``$`` argument, despite being the *tag function*, it has also various - methods (which we call the [Tag API](#tag-api)) providing several extra - functionalities. - - * In fact, ``${"value"}`` is just a shorthand for ``${$.arg("value")}`` (or - ``${X.arg("value")}`` if ``X`` is used instead of ``$``). - - - -### Arguments declaration - -### Alternative SQL - -### Default Engine - -### Data - -(Not yet implemented...) - -### with - -(Not yet implemented...) - -API REFERENCE -------------- - - -*SQLTT* involves two API interfaces: - - * [Template API](#template-api): That is the methods we have available from - any *SQLTT* instantiated template. - - * [Tag API](#tag-api): Consisting on various methods attached to the tag - function our template will receive during its compilation. - - -### Template API - -After instantiating our template as SQLTT (`const myQuery = new -sqltt(_my_template_)`), we are allowed to use below methods: - - -#### sql(engFlavour) - - - - -**Arguments:** - - * *engName:* (Optional) - - -#### args(argData) - - -**Arguments:** - - * *argData:* Can be a simple array - - - -#### concat(str) - - - -#### options(optsObject) - - - -Let to override [initially specified options:](#accepted-options). - -It returns a new sqltt instance identical to original except for the modified -options. - -Options not specified in provided *optsObject* will remain the same as it were -specified in the original instance. - - - -### Tag API - -Tag API methods outputs rendered SQL substrings in the propper syntax for -targetted database engine. - -> 📌 Further examples will follow PostgreSQL syntax unless otherwise said. - - -#### arg() - - - -Provide the ability to interpolate an argument by its name. - -Argument names can be repeated. They will be rendered in apparition order when -[args() Template API meghod](#argsargdata) called unless different order were -specified through [args property](#arguments-declaration) in template source. - -**📝 SYNTAX:** - -```javascript -arg(argName[, alias]) -``` - -**🗃️ PARAMETERS:** - - * *argName:* Argument name. - * *alias:* (Optional) Alias. - -**🏃 SHORTHAND:** - -In its simplest form (when *argName* is string and *alias* is not provided) -simple string can be used as a *shorthand*. - -> **🗂️ Examples:** -> -> * Explicit: `${$.arg("argName")}` ➡ `$argName`. -> * Using shorthand: `${"argName"}` ➡ `$argName`. - - -**🚀 ENHNANCED BEHAVIOUR:** - - * If *alias* is provided, it is added after argument interpolation. - -> **🗂️ Example:** -> -> * `${$.arg("foo", "afoo")}` ➡ `$foo as afoo`. - -
- -> 📌 Arguments aren't usually aliased in a query, but they can be placed in the -> projection too in order to complete it with constant data. - - * Passing an object as *argName* it will interpolate all keys as comma - separated SQL arguments using their values as alias (*alias* argument, if - given will be ignored). - - Boolean false will disable alias for given key. - - Boolean true make key to be used as alias instead. - -> **🗂️ Example:** -> -> * `${$.arg({foo: "afoo", bar: false, baz: true})}` ➡ `$foo as afoo, $bar, -> $baz as baz`. - - - * Using an array instead, will produce the same effect without aliases (if - *alias* not given or evaluates to false) or using the same name (else - case). - -> **🗂️ Example:** -> -> * `${$.arg([foo, bar])}` ➡ `$foo, $bar`. -> * `${$.arg([foo, bar], true)}` ➡ `$foo as foo, $bar as bar`. - -
- -> ⚠ The "as" keyword in previous examples had been written for the sake of -> clarity only. -> -> No "as" keyword is currently rendered as it is invalid in many database engines -> and, for those which accept it, it is optional anyway. Future SQLTT versions -> may render it for engines that support it. - - - -#### include() - - - -Provide the ability to nest other templates. - -**📝 SYNTAX:** - -```javascript -include(src [, bindings]) -``` - -**🗃️ PARAMETERS:** - - * *src:* SQLTT instance or any valid SQLTT source (including raw string). - * *bindings:* (Optional) Argument bindings. - -**🏃 SHORTHAND:** - -If *src* is an already instantiated SQLTT template and no bindings are needed, -you don't need to use *.include()* at all. - - -> **🗂️ Examples:** -> -> Considering this simple snippet: -> ```javascript -> const src0 = $=>$`select foo from bar where baz = ${"baz"}`; -> const q0 = new sqltt(src0) -> ``` -> -> * Explicit: `insert into sometable ${$.include(q0)}` ➡ `insert into -> sometable select foo from bar where baz = $baz`. -> * Using shorthand: `insert into sometable ${q0}` ➡ `insert into sometable -> select foo from bar where baz = $baz`. -> * From Source (using *.include()* required): `insert into sometable -> ${$.include(src0)}` ➡ `insert into sometable select foo from bar where -> baz = $baz`. - - -**🚀 ENHNANCED BEHAVIOUR:** - - -**🗂️ EXAMPLES:** - - - - -#### keys(), values() and entries() - -**📝 SYNTAX:** - -```javascript - keys(argSpec [, sep [, wrapStr]]) - values(argSpec [, sep [, wrapStr]]) - entries(argSpec [, sep [, wrapStr]]) -``` - - -#### literal(str) - - - -Having regular strings are normally interpreted as shorthand for simple -argument interpolations, ``.literal()`` provide a way to inject a raw string. - -Since ``select ${$.literal("foo")} from bar`` is the exact same of ``select foo -from bar`` (with no interpolation at all), ``.literal()`` is mostly used -internally by other *Tag API* functions. - -But it can also be useful in case we need to insert some calculated substring. - -**🗂️ Example:** - -```javascript -tpl.getUserData = new sqltt($ => ({ - sql: $` - select * - from ${$.literal(get_table_name("users"))} - where user_id = ${"user_id"} - `, -})); -``` - - -#### data(str) - - -### Static Methods - - -#### publish(module, tpl) - - TODO: Rewrite more detailed... - - * Publishing helper: ``sqltt.publish(module, myTpl);`` - - Assigns ``myTpl`` to ``module.exports`` (so exports it). - - If template file is directly invoked, outputs *CLI* SQL to stdout. - - ``node myTpl.sql.js`` outputs general cli output. - - ``SQL_ENGINE=postgresql node myTpl.sql.js`` outputs postgresql - flavoured CLI output. - - ... or simply ``node --postgresql myTpl.sql.js``. - - If myTpl is a *key: value* object instead, first argument is expected to - select which query is required. - - Ex.: ``node myTplLib.sql.js listQuery`` - - If no argument provided in this case, a list of available keys will be - shown instead. - - Arguments are wrapped in a *set* commands. - - Ex.: ``node myTpl.sql parameter1 parameter2 "third parameter"`` - - Ex.: ``node myTplLib.sql listBySection sectionId`` - - -Advanced Features ------------------ - -### Hooks - - - -Hooks lets us to wrap arguments differently according to the actual engine. - -They consist on a function that takes the original string and the engine name. -If this function returns a non falsy value, the argument is replaced by that in -the query. Otherwise it remains untouched. - -> 📌 Hooks can also be applied to non argument keywords. To do so we need -> interpolate them using [literal() tag method](#literalstr). - - -to escape them as if it were real arguments and then wrap it as an array. This -will avoid its interpolation as argument. -> - -**Example:** - -```javascript -tpl.getUserData = new sqltt({ - hooks: { - // Prettier formatting on CLI output: - user_profile: (arg, eng) => eng.match(/(^|_)cli$/) && "jsonb_pretty("+arg+") as "+arg, - // Rename "bigint" cast to - bigint: (arg, eng) => eng.match(/^oracle/) && "int", - }, - sql: $=>$` - select id, name, ${$.literal("user_profile")} - from users - where cast(strCtime as ${$.literal("bigint")}) > ${"fromTimestamp"} - `, -}); -``` - -There's a shorthand consisting in to simply specify an alternative string. In -this case the replacement would be done inconditionally. But this could be -helpful in case we want to manually enable/disable some tweaks without editing -the actual SQL (just commenting in and out that hook). - -**🗂️ Example:** - -```javascript - // If we wanted to apply this hook to all engines: - json_data: (arg, eng) => eng.match(/(^|_)cli$/) && "jsonb_pretty("+arg+") as "+arg, - // We could have written it as: - json_data: "jsonb_pretty(%) as %", - -``` - - - -### SQL Alternatives - - - -If it is impossible or unreasonable to use the same sql structure for some -database engines, *sqltt* allows to specify a completely different sql source -for given database through *altsql* property. - -**🗂️ Example:** - -```javascript -tpl.someQuery = new sqltt({ - sql: $=>$` - /* Regular SQL */ - `, - altsql: { - oracle: $=>$` - /* Oracle specific SQL */ - ` - } -}); -``` - -> 📌 Argument names and order are checked to be the same in all query -> alternatives to ensure its consistency so using *args* property to fix their -> order is hardly encouraged. - - - -### String Concatenation - - - -*sqltt* template instances provide a ``.concat()`` method returning a new -instance whose ``sql()`` method will return provided string -concatenated at the end. - -This is useful to add simple clauses such as ``limit``, ``order by`` or ``group by`` -from our application logic. - -**🗂️ Example:** - -```javascript -const myQuery = require('path/to/myQuery.sql.js') - .concat("limit 100") -; -db.queryRows( - myQuery.sql("postgresql") - , myQuery.args(inputData) // or simply "inputData" if db is sqltt aware* - // (*) Such as ppooled-pg -).then(rows=>console.log(rows); -``` - - - -TODO ----- - - * Implement Custom Engines: - - Engines are really simple libraries very easy to implement. But nowadays - they must be included in SQLTT package. - - Custom engines will allow users to implement their own engines extending - default ones just passing them as a specific option. - - * Implement customisation opitons: - - Smart indentation. - - Comments Removal. - - * Incorporate Oracle comptibility Shims - - https://wiki.postgresql.org/wiki/Oracle_to_Postgres_Conversion - - http://www.sqlines.com/postgresql-to-oracle#most-complex-migration-issues - - -Contributing ------------- - -If you are interested in contributing with this project, you can do it in many ways: +--- - * Creating and/or mantainig documentation. +## Features at a Glance - * Implementing new features or improving code implementation. +- **Single source, multiple engines** — render PostgreSQL, Oracle, and generic SQL + from one template. +- **Named arguments** — use `${"argName"}` instead of `$1`, `$2`, … +- **Query composition** — nest sub-templates: `${otherQuery}`. +- **INSERT / UPDATE helpers** — `$.keys()`, `$.values()`, `$.entries()`. +- **CLI support** — pipe rendered SQL directly to any database interpreter. +- **Non-JS language support** — compile queries to plain `.sql` files. +- **Hooks** — transform argument rendering per engine. +- **SQL alternatives** (`altsql`) — engine-specific overrides when needed. +- **Mutable queries** (`data` / `mutation`) — parameterise column lists and filters. +- **CTEs** (`with`) — declare Common Table Expression dependencies. +- **Query caching** — compiled SQL is memoised per engine. +- **Debug option** — log `.args()` calls for specific queries. - * Reporting bugs and/or fixing it. +--- - * Sending me any other feedback. +## Contributing - * Whatever you like... +Contributions are welcome: -Please, contact-me, open issues or send pull-requests thought [this project GIT repository](https://github.com/bitifet/sqltt) +- 📝 Documentation improvements. +- ✨ New features or engine support. +- 🐛 Bug reports and fixes. +- 💬 Any other feedback. +Please open issues or pull requests on the +[GitHub repository](https://github.com/bitifet/sqltt). diff --git a/SKILLS.md b/SKILLS.md new file mode 100644 index 0000000..512b332 --- /dev/null +++ b/SKILLS.md @@ -0,0 +1,172 @@ +# SQLTT — Copilot Skill + +This file describes how to use **SQLTT** as a GitHub Copilot Skill, enabling +AI-assisted SQL query generation, review, and refactoring inside your editor. + +--- + +## What the Skill Does + +When activated, the SQLTT skill gives Copilot deep understanding of: + +- The SQLTT template syntax and API. +- How to write, compose, and optimise SQLTT query files. +- Best practices for multi-engine SQL (PostgreSQL, Oracle, …). +- Argument handling patterns (`.arg()`, `.args()`, `.keys()`, `.values()`, `.entries()`). +- Advanced patterns (hooks, mutations, CTEs, SQL alternatives). + +--- + +## Activating the Skill + +### Option 1 — Reference this file from your `.github/copilot-instructions.md` + +Create (or edit) `.github/copilot-instructions.md` in your project and add: + +```markdown +## SQLTT Skill + +This project uses the [SQLTT](https://github.com/bitifet/sqltt) library for +managing SQL queries via Tagged Templates. + +When writing or reviewing SQL template files (*.sql.js): +- Use the SQLTT template format described in + https://github.com/bitifet/sqltt/blob/master/docs/TEMPLATE_FORMAT.md +- Apply the Tag API methods documented in + https://github.com/bitifet/sqltt/blob/master/docs/API.md +- Follow the patterns shown in + https://github.com/bitifet/sqltt/blob/master/docs/EXAMPLES.md +``` + +### Option 2 — Add SQLTT context to a Copilot Chat session + +Paste this prompt at the start of a Copilot Chat session to give it full +SQLTT context: + +``` +I am using the SQLTT library (https://github.com/bitifet/sqltt). +Please help me write SQL templates using the SQLTT Tagged Template syntax. +Key facts: +- Templates are created with: new sqltt($=>$`...`) +- Arguments are interpolated as: ${"argName"} (shorthand for ${$.arg("argName")}) +- Sub-templates are nested as: ${otherTemplate} (shorthand for ${$.include(otherTemplate)}) +- INSERT helpers: ${$.keys(fields)}, ${$.values(fields)} +- UPDATE helpers: ${$.entries(fields)} +- Publish for CLI + require() usage: sqltt.publish(module, tpl) +- Full docs: https://github.com/bitifet/sqltt/blob/master/docs/ +``` + +--- + +## Skill Capabilities + +### Generate a Query Template + +Ask Copilot to generate a SQLTT template for a given table and operation: + +> **Prompt:** "Write a SQLTT template for a `users` table with queries: `list`, +> `show`, `insert`, `update`, `delete`." + +Expected output pattern: + +```javascript +const sqltt = require("sqltt"); + +const commonFields = ["name", "email", "role"]; +const tpl = {}; + +tpl.list = new sqltt($=>$` + select id, name, email, role + from users + order by name +`); + +tpl.show = new sqltt($=>$` + select id, name, email, role, ctime + from users + where id = ${"id"} +`); + +tpl.insert = new sqltt($=>$` + insert into users (${$.keys(commonFields)}) + values (${$.values(commonFields)}) +`); + +tpl.update = new sqltt($=>$` + update users set ${$.entries(commonFields)} + where id = ${"id"} +`); + +tpl.delete = new sqltt($=>$` + delete from users + where id = ${"id"} +`); + +sqltt.publish(module, tpl); +``` + + +### Review an Existing Template + +Ask Copilot to review a template file for correctness or improvements: + +> **Prompt:** "Review this SQLTT template file. Check for correct argument +> declarations, missing `args` ordering, and opportunities to use `.keys()` / +> `.values()` / `.entries()`." + + +### Add Multi-Engine Support + +Ask Copilot to add an Oracle-specific `altsql` to an existing template: + +> **Prompt:** "Add Oracle support to this SQLTT template using `altsql`." + + +### Convert a Plain SQL String to SQLTT + +> **Prompt:** "Convert this raw SQL string to a SQLTT template, replacing `$1`, +> `$2` placeholders with named arguments." + + +### Add a CTE + +> **Prompt:** "Refactor this query to use a CTE via the SQLTT `with` property." + + +--- + +## Quick API Cheatsheet for Copilot + +| Task | SQLTT pattern | +|:-----|:--------------| +| Declare a template | `new sqltt($=>$\`...\`)` | +| Named argument | `${"argName"}` or `${$.arg("argName")}` | +| Fixed argument order | `{ args: ["a", "b"], sql: $=>$\`...\` }` | +| Nest a sub-query | `${subQuery}` or `${$.include(subQuery)}` | +| Nest with bound args | `${$.include(subQuery, {key: "value"})}` | +| INSERT columns | `${$.keys(fields)}` | +| INSERT values | `${$.values(fields)}` | +| UPDATE set | `${$.entries(fields)}` | +| Conditional clause | `${$.entries($.data("filters"), "and", "where %")}` | +| Raw string injection | `${$.literal("raw_sql")}` | +| SQL comment | `${$.REM("comment")}` | +| Render SQL | `tpl.sql("postgresql")` | +| Generate args array | `tpl.args({ argName: value })` | +| Append a clause | `tpl.concat("limit 100")` | +| Override options | `tpl.options({ debug: true })` | +| Mutate data | `tpl.data({ columns: ["id", "name"] })` | +| Publish (CLI + require) | `sqltt.publish(module, tpl)` | +| Engine-specific SQL | `{ sql: $=>$\`...\`, altsql: { oracle: $=>$\`...\` } }` | + + +--- + +## Further Reading + +- [README](README.md) — Overview and quick-start +- [Examples](docs/EXAMPLES.md) — Annotated examples +- [Usage Guide](docs/USAGE.md) — Installation and usage patterns +- [Template Format](docs/TEMPLATE_FORMAT.md) — All template source properties +- [API Reference](docs/API.md) — Complete method reference +- [Advanced Features](docs/ADVANCED.md) — Hooks, mutations, CTEs +- [Engines](docs/ENGINES.md) — Engine system and multi-database support diff --git a/docs/ABOUT.md b/docs/ABOUT.md new file mode 100644 index 0000000..95fcc81 --- /dev/null +++ b/docs/ABOUT.md @@ -0,0 +1,95 @@ +# SQLTT — About & Roadmap + +--- + +## About SQLTT + +SQL is a powerful language, but most databases ship with their own syntax +variations and nuances. Even for the same database, the syntax used by +application libraries and CLI interpreters typically differs — at least for +parameterized queries. + +This often forces developers to modify queries back and forth to test them in a +database CLI, or — worse — to maintain completely separate query versions for +each supported database. + +ORM solutions address this at the cost of generating suboptimal queries and +blocking access to powerful, database-specific SQL features. + +**SQLTT** lets you maintain a single version of each query while preserving the +full power of real SQL. It embraces the +[DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) principle and +provides advanced features such as snippet/query reuse and much more. + +> 💡 **Origin:** SQLTT grew out of +> [this StackOverflow answer](https://stackoverflow.com/a/41136912/4243912), +> progressively evolving until it became too large for a single file. + + +--- + +## About This Prerelease + +SQLTT is currently published as a **1.0.0-x prerelease**. The next stable +release will be **1.0.0** and will include several breaking changes relative to +the 0.x line. + +The prerelease exists because useful, already-implemented features were needed +in a real project before the full 1.0.0 goals (complete documentation + test +suite) were met. + +New ideas (`.keys()`, `.values()`, `.entries()`, enhanced operations, CTE +support, mutations, etc.) kept arriving during development, repeatedly +deferring the 1.0.0 release. + + +--- + +## Roadmap + +### In Progress / Planned + +#### Fix `.concat()`, `.limit()` and `.wrap()` through Wrapping Engines + +`.concat()`, `.limit()` and `.wrap()` currently wrap the output of the +original `.sql()` call. This is incorrect when engines such as Oracle provide +their own wrapping, resulting in an inverted wrapping order. + +#### Implement More CLI Modifiers + +- Implement `--all` to render all defined templates (preceded by a name + comment), with arguments declared globally at the top. + +#### Complete Mutable Queries Implementation + +Several advanced mutation features are planned (see details below). Most +foundational pieces are already implemented: + +- ✅ `.data(key)` Tag API method +- ✅ `wrapStr` parameter for `.keys()`, `.values()`, `.entries()` +- ✅ `.data()` Template API method +- ✅ `.data()` presets +- ✅ CLI mutations with preset support +- ⬜ Allow multiple mutations with merged/concatenated keys +- ⬜ Rename `.data()` to `.mutation()` and provide a simpler `.data()` replacement +- ⬜ Support preset callbacks with parameters and Tag API binding + +#### Complete CTE "Dependency" System + +- ✅ `with` template property for CTE declarations +- ✅ Recursive CTE resolution and deduplication +- ⬜ Accept arrays or single items (requires mandatory `name` attribute) +- ⬜ Allow CTE-carrying queries to be included via `$.include()` without error + +#### Other + +- ⬜ Add `.options()` methods to `.publish()` exports (not strictly required for 1.0.0) +- ⬜ Auto-name unnamed templates in multi-query files by their object key + +### Future + +- **Custom Engines:** Allow users to implement and inject their own engines + without modifying the SQLTT package. +- **Smart Indentation** and **Comment Removal** options. +- **Oracle Compatibility Shims** — see + [PostgreSQL→Oracle migration notes](https://wiki.postgresql.org/wiki/Oracle_to_Postgres_Conversion). diff --git a/docs/ADVANCED.md b/docs/ADVANCED.md new file mode 100644 index 0000000..a8787c0 --- /dev/null +++ b/docs/ADVANCED.md @@ -0,0 +1,202 @@ +# SQLTT — Advanced Features + +--- + +## Table of Contents + +- [Hooks](#hooks) +- [SQL Alternatives (altsql)](#sql-alternatives-altsql) +- [String Concatenation](#string-concatenation) +- [Mutable Queries (data / mutation)](#mutable-queries-data--mutation) + - [Template data Property](#template-data-property) + - [data() Template API Method](#data-template-api-method) + - [CLI Mutations](#cli-mutations) + - [Presets](#presets) +- [CTEs (with)](#ctes-with) + +--- + +## Hooks + +Hooks let you transform argument interpolations differently depending on the +target engine. They are declared in the `hooks` property of the template source +object. + +Each hook is a function `(arg, eng) => replacement` where: + +- `arg` is the current rendered argument string. +- `eng` is the engine name. +- Return a **truthy** string to replace `arg`; return falsy to leave it unchanged. + +A string shorthand replaces unconditionally (using `%` as the argument +placeholder). + +**Example:** + +```javascript +tpl.getUserData = new sqltt({ + hooks: { + // Pretty-print JSON in CLI output + user_profile: (arg, eng) => + eng.match(/(^|_)cli$/) && `jsonb_pretty(${arg}) as ${arg}`, + // Rename the cast keyword for Oracle + bigint: (arg, eng) => + eng.match(/^oracle/) && "int", + // Unconditional shorthand + json_data: "jsonb_pretty(%) as %", + }, + sql: $=>$` + select id, name, ${$.literal("user_profile")} + from users + where cast(strCtime as ${$.literal("bigint")}) > ${"fromTimestamp"} + `, +}); +``` + +> 📌 Hooks can also be applied to non-argument keywords by interpolating them +> with [`$.literal()`](API.md#literalstr). + +--- + +## SQL Alternatives (altsql) + +When a query cannot be expressed identically for all databases, `altsql` +provides engine-specific overrides while keeping a single source file. + +```javascript +tpl.someQuery = new sqltt({ + sql: $=>$` + /* Standard SQL */ + `, + altsql: { + oracle: $=>$` + /* Oracle-specific SQL */ + `, + }, +}); +``` + +> 📌 Argument names and order are validated to be identical across all +> alternatives. Using the `args` property to fix the order is strongly +> recommended when `altsql` is present. + +--- + +## String Concatenation + +`.concat(str)` returns a new SQLTT instance whose `sql()` output has `str` +appended. Use it to add dynamic clauses (`LIMIT`, `ORDER BY`, `GROUP BY`, …) +from application logic without modifying the template itself. + +```javascript +const myQuery = require('path/to/myQuery.sql.js') + .concat("limit 100"); + +db.query( + myQuery.sql("postgresql"), + myQuery.args(inputData), +); +``` + +--- + +## Mutable Queries (data / mutation) + +The `data` feature allows you to parameterise structural parts of a query — +column lists, filter sets, etc. — keeping a single SQL template while +dynamically adjusting what it selects or filters. + +### Template data Property + +Declare static data in the template source: + +```javascript +tpl.list = new sqltt({ + data: { + columns: ["id", "dptName", "name"], + }, + sql: $=>$` + select ${$.arg($.data("columns"))} + from personnel + join depts using(dptId) + ${$.entries($.data("filters"), "and", "where %")} + `, +}); +``` + +### data() Template API Method + +Call `.data(patch)` on an instance to get a new instance with modified data: + +```javascript +const listWithBirth = tpl.list.data({ columns: ["id", "name", "birth"] }); +``` + +### CLI Mutations + +From the CLI, add a mutation in parentheses after the query name: + +```sh +node personnel.sql.js 'list({filters: ["dptId"]})' oper | psql tiaDB +``` + +### Presets + +Define named presets in the template source and invoke them by name: + +```javascript +tpl.list = new sqltt({ + data: { columns: ["id", "dptId", "dptName", "name"] }, + presets: { + detailed: { columns: ["id", "dptId", "dptName", "name", "sex", "ctime"] }, + bySection: { filters: ["dptId"] }, + }, + sql: $=>$` + select ${$.arg($.data("columns"))} + from personnel + join depts using(dptId) + ${$.entries($.data("filters"), "and", "where %")} + `, +}); +``` + +CLI usage with presets: + +```sh +node personnel.sql.js 'list(bySection)' | psql tiaDB +node personnel.sql.js 'list(bySection,detailed)' | psql tiaDB +``` + +--- + +## CTEs (with) + +Use the `with` property to declare Common Table Expressions that a query +depends on. SQLTT resolves CTE dependencies recursively and deduplicates them. + +```javascript +const baseCTE = new sqltt({ + name: "active_users", + sql: $=>$`select * from users where active = true`, +}); + +const myQuery = new sqltt({ + with: { active_users: baseCTE }, + sql: $=>$` + select id, name + from active_users + where role = ${"role"} + `, +}); +``` + +The rendered SQL will include: + +```sql +WITH active_users AS ( + select * from users where active = true +) +select id, name +from active_users +where role = $1 +``` diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..a32a308 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,324 @@ +# SQLTT — API Reference + +--- + +## Table of Contents + +- [Template API](#template-api) + - [sql(engFlavour)](#sqlengflavour) + - [args(argData)](#argsargdata) + - [concat(str)](#concatstr) + - [options(optsObject)](#optionsoptsobject) + - [data(dataPatch)](#datadatapatch) +- [Tag API](#tag-api) + - [arg()](#arg) + - [include()](#include) + - [keys(), values() and entries()](#keys-values-and-entries) + - [literal(str)](#literalstr) + - [data(key)](#datakey) + - [REM(str)](#remstr) +- [Static Methods](#static-methods) + - [publish(module, tpl)](#publishmodule-tpl) + +--- + +## Template API + +These methods are available on any instantiated SQLTT template +(`const myQuery = new sqltt(source)`). + + +### sql(engFlavour) + +Renders the template as an SQL string for the requested engine flavour. + +**Arguments:** + +- `engFlavour` *(Optional)* — Engine name (e.g. `'postgresql'`, `'oracle_cli'`, + `'cli'`). Defaults to the `default_engine` option or `'default'`. + +**Returns:** `string` + +**Example:** + +```javascript +const sql = myQuery.sql('postgresql'); +// "select id, name from users where id = $1" +``` + + +### args(argData) + +Generates a properly ordered arguments array from a key/value object. + +**Arguments:** + +- `argData` — A `{key: value, …}` object. Missing keys default to `null`; + extra keys are silently ignored. + +**Returns:** `Array` + +**Example:** + +```javascript +const args = myQuery.args({ userId: 42, extraField: "ignored" }); +// [42] (if userId is the only declared arg) +``` + + +### concat(str) + +Returns a **new** SQLTT instance whose `sql()` output has `str` appended. +Useful for adding clauses like `LIMIT`, `ORDER BY`, or `GROUP BY` from +application logic. + +**Arguments:** + +- `str` — String to append. + +**Returns:** New SQLTT instance. + +**Example:** + +```javascript +const limited = myQuery.concat("limit 100"); +const sql = limited.sql('postgresql'); +``` + + +### options(optsObject) + +Returns a **new** SQLTT instance identical to the original except for the +overridden options. Options not mentioned in `optsObject` are preserved. + +**Arguments:** + +- `optsObject` — Partial options object (see [Accepted Options](USAGE.md#accepted-options)). + +**Returns:** New SQLTT instance. + + +### data(dataPatch) + +Returns a **new** SQLTT instance with its `data` property patched. Each entry +in `dataPatch` replaces the corresponding entry in the template's `data` +object. Set a key to `null` or `undefined` to remove it. + +**Arguments:** + +- `dataPatch` — Object, preset name (string), array of preset names, or + comma-separated preset list. + +**Returns:** New SQLTT instance. + +--- + +## Tag API + +Tag API methods are available on the `$` argument inside a SQL callback +(`$=>$\`…\``). They render SQL sub-strings in the correct syntax for the +target engine. + +> 📌 All examples below use PostgreSQL syntax. + + +### arg() + +Interpolates an argument by name. + +**Syntax:** + +```javascript +arg(argName[, alias]) +``` + +**Parameters:** + +- `argName` — Argument name (string, object, or array). +- `alias` *(Optional)* — Column alias to append. + +**Shorthand:** + +`${"argName"}` is equivalent to `${$.arg("argName")}`. + +**Examples:** + +```javascript +// Basic +${"userId"} // → $1 (positional) +${$.arg("userId")} // → $1 + +// With alias +${$.arg("profile", "userProfile")} // → $2 as userProfile + +// Object (key → alias) +${$.arg({foo: "afoo", bar: false, baz: true})} +// → $foo as afoo, $bar, $baz as baz + +// Array (no aliases) +${$.arg(["foo", "bar"])} // → $foo, $bar +// Array (self-alias) +${$.arg(["foo", "bar"], true)} // → $foo as foo, $bar as bar +``` + +> ⚠️ The `as` keyword is omitted in the rendered SQL because it is invalid in +> some engines and optional in those that accept it. + + +### include() + +Nests another SQLTT template inside the current one. + +**Syntax:** + +```javascript +include(src [, bindings]) +``` + +**Parameters:** + +- `src` — SQLTT instance or any valid SQLTT source (including a raw string). +- `bindings` *(Optional)* — `{argName: fixedValue, …}` to bind arguments to + constant values in the included sub-query. + +**Shorthand:** + +If `src` is an already-instantiated SQLTT template and no bindings are needed, +you can interpolate it directly: `${mySubQuery}`. + +**Examples:** + +```javascript +// Explicit +$`insert into t ${$.include(subQuery)}` + +// Shorthand (instantiated template, no bindings) +$`insert into t ${subQuery}` + +// From source (must use .include()) +$`insert into t ${$.include($=>$`select 1`)}` + +// With fixed binding +$`insert into t ${$.include(subQuery, {status: "active"})}` +``` + + +### keys(), values() and entries() + +Render parts of an `INSERT` or `UPDATE` statement from a field specification. + +**Syntax:** + +```javascript +keys(argSpec [, sep [, wrapStr]]) +values(argSpec [, sep [, wrapStr]]) +entries(argSpec [, sep [, wrapStr]]) +``` + +**Parameters:** + +- `argSpec` — Array of field names, a data key (string), or an object. +- `sep` *(Optional)* — Separator string (default: `','`). +- `wrapStr` *(Optional)* — Wrapping template containing `%` as placeholder; + rendered only if the method produces output. Useful for conditional `WHERE` + or `SET` clauses. + +**Examples:** + +```javascript +const fields = ["name", "sex", "birth"]; + +// INSERT +$`insert into personnel (${$.keys(fields)}) values (${$.values(fields)})` +// → insert into personnel (name, sex, birth) values ($1, $2, $3) + +// UPDATE +$`update personnel set ${$.entries(fields)} where id = ${"id"}` +// → update personnel set name=$1, sex=$2, birth=$3 where id=$4 + +// Conditional WHERE with wrapStr +$`select * from t ${$.entries($.data("filters"), "and", "where %")}` +// → select * from t where name=$1 and sex=$2 (when filters is non-empty) +// → select * from t (when filters is empty) +``` + + +### literal(str) + +Injects a raw string into the SQL without any argument interpolation. + +**Example:** + +```javascript +const tableName = getTableName("users"); +$`select * from ${$.literal(tableName)} where id = ${"id"}` +// → select * from users where id = $1 +``` + + +### data(key) + +Retrieves a value from the template's `data` object for use with other Tag API +methods. + +**Example:** + +```javascript +new sqltt({ + data: { columns: ["id", "name", "birth"] }, + sql: $=>$` + select ${$.arg($.data("columns"))} + from users + where id = ${"id"} + `, +}) +``` + + +### REM(str) + +Injects an SQL comment. Useful for inline documentation inside templates. + +**Example:** + +```javascript +$` + ${tpl.list} ${$.REM("Re-uses the list query")} + where dptId = ${"dptId"} +` +``` + +--- + +## Static Methods + + +### publish(module, tpl) + +The recommended way to export a template file. Enables both `require()` usage +and direct CLI invocation. + +**Arguments:** + +- `module` — The Node.js `module` object (pass it as-is). +- `tpl` — A SQLTT instance or a `{key: sqlttInstance, …}` object. + +**Behaviour:** + +- Assigns `tpl` to `module.exports`. +- When the file is executed directly (`node myTpl.sql.js …`): + - Single template — renders CLI SQL to stdout. + - Multi-template with no argument — lists available query names. + - Multi-template with a query name — renders that query's CLI SQL. + - Arguments after the query name are wrapped in `\set` / `DEFINE` + declarations (engine-dependent) and prepended to the output. + +**Examples:** + +```sh +node myTpl.sql.js # list queries (multi-template) +node myTpl.sql.js listQuery # render 'listQuery' +node myTpl.sql.js listQuery sectionId # render with argument +node myTpl.sql.js myQuery --postgresql # override engine +node myTpl.sql.js myQuery | psql mydb # execute directly +``` diff --git a/docs/ENGINES.md b/docs/ENGINES.md new file mode 100644 index 0000000..3cd41d1 --- /dev/null +++ b/docs/ENGINES.md @@ -0,0 +1,106 @@ +# SQLTT — Engines + +--- + +## Table of Contents + +- [Overview](#overview) +- [Currently Supported Engines](#currently-supported-engines) +- [Engine Flavours and Targets](#engine-flavours-and-targets) +- [Selecting an Engine](#selecting-an-engine) + - [SQL\_ENGINE Environment Variable](#sql_engine-environment-variable) + - [--engine CLI Flag](#--engine-cli-flag) +- [Adding More Database Engines](#adding-more-database-engines) + +--- + +## Overview + +SQLTT is database-agnostic: it only provides a templating layer. You are +responsible for writing SQL that is valid for your target database(s). + +The main difference between databases is how **positional parameters** are +expressed: + +| Database | Application library | CLI interpreter | +|:-----------|:--------------------|:-------------------| +| PostgreSQL | `$1`, `$2`, … | `:var1`, `:var2`, … | +| Oracle | `:1`, `:2`, … | `'&var1'`, `'&var2'`, … | + +To handle these differences, SQLTT uses small specialised libraries called +**engines**. + +--- + +## Currently Supported Engines + +| Name | Description | +|:-------------------|:----------------------------------------------------| +| `default` | Generic / ANSI-compatible SQL. | +| `default_cli` | Generic SQL suitable for CLI interpreters. | +| `postgresql` | PostgreSQL-specific SQL. | +| `postgresql_cli` | PostgreSQL SQL for the `psql` CLI client. | +| `oracle` | Oracle-specific SQL. | +| `oracle_cli` | Oracle SQL for the `sqlplus` CLI client. | + +--- + +## Engine Flavours and Targets + +Each database has two engine variants: + +- **application engine** (`postgresql`, `oracle`, …) — SQL formatted for use + in a database driver / application library. +- **CLI engine** (`postgresql_cli`, `oracle_cli`, …) — SQL formatted for + direct execution in a database CLI interpreter (includes `\set` / `DEFINE` + variable declarations, etc.). + +When you call `.sql()` from an application you usually pass a flavour name +such as `'postgresql'`. When generating SQL for CLI usage you can simply pass +`'cli'`, which selects `default_cli` by default and can be overridden by the +`SQL_ENGINE` environment variable. + +--- + +## Selecting an Engine + +### SQL\_ENGINE Environment Variable + +Set `SQL_ENGINE` before running your template script to select an engine: + +```sh +# Use PostgreSQL CLI syntax +export SQL_ENGINE=postgresql +node myTpl.sql.js myQuery | psql mydb + +# One-off override +SQL_ENGINE=oracle node myTpl.sql.js myQuery | sqlplus user/pass@mydb +``` + +Special values: + +| Value | Effect | +|:-----------------|:------------------------------------------------------------| +| `cli` | Use `default_cli`; can be overridden by `SQL_ENGINE`. | +| `nocli` | Override the CLI engine of the selected flavour with nocli. | +| `*_nocli` | Render application SQL (no `\set` / `DEFINE`) for non-JS consumers. | + + +### --engine CLI Flag + +You can also pass `--engine=` (or the shorthand `--`) directly on +the command line. This overrides `SQL_ENGINE`: + +```sh +node myTpl.sql.js myQuery --engine=oracle +node myTpl.sql.js myQuery --postgresql +node myTpl.sql.js myQuery --oracle_cli +``` + +--- + +## Adding More Database Engines + +Engines are small, easy-to-implement libraries in `lib/engines.js`. If you +want to add support for a new database, check that file for existing examples +and feel free to open a pull request with your additions. diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md new file mode 100644 index 0000000..7072510 --- /dev/null +++ b/docs/EXAMPLES.md @@ -0,0 +1,242 @@ +# SQLTT — Examples + +> 📌 Unless stated otherwise, all examples below target *PostgreSQL* engines. +> When no engine is specified, a "generic" one is used (currently identical to +> the PostgreSQL engine). + +--- + +## Table of Contents + +- [Template Syntax](#template-syntax) + - [Syntax Highlighting](#syntax-highlighting) + - [Block-specific Syntax Highlighting in Vim](#block-specific-syntax-highlighting-in-vim) +- [Executing from CLI](#executing-from-cli) + - [Types and Quoting](#types-and-quoting) +- [Using from a Node.js Application](#using-from-a-nodejs-application) + - [Single Template](#single-template) + - [Multiple Templates](#multiple-templates) +- [Using from Non-JavaScript Languages](#using-from-non-javascript-languages) + +--- + +## Template Syntax + +The following example shows what a *SQLTT* template file looks like and how it +can generate actual SQL for multiple database engines or be executed directly +via the CLI. + +**`personnel.sql.js`** + +```javascript +const sqltt = require("sqltt"); +const commonFields = ["dptId", "name", "sex", "birth"]; + +const tpl = {}; + +tpl.list = new sqltt(` + select id, dptName, name + from personnel + join depts using(dptId) +`); + +tpl.listByDept = new sqltt($=>$` + ${tpl.list} ${$.REM("Same as ${$.include(tpl.list)}")} + where dptId = ${"dptId"} ${$.REM("Same as ${$.arg('dptId')}")} +`); + +tpl.show = new sqltt($=>$` + select id, dptName, name, birth, ctime + from personnel + join depts using(dptId) + where id = ${"id"} +`); + +tpl.insert = new sqltt($=>$` + insert into personnel (${$.keys(commonFields)}) + values (${$.values(commonFields)}) +`); + +tpl.update = new sqltt($=>$` + update personnel set ${$.entries(commonFields)} + where id = ${"id"} +`); + +sqltt.publish(module, tpl); // Export and make available from CLI +``` + +> 📌 More complete examples are in the +> [`examples/` directory](../examples/). + + +### Syntax Highlighting + +Because *SQLTT* template files are plain JavaScript you normally get JS +highlighting. You may prefer SQL highlighting inside the template strings, or +even both. + +The simplest option is to use a `.sql.js` extension (JS highlighting) or +`.js.sql` (SQL highlighting), but you cannot have both that way. + +A better approach is to keep JavaScript highlighting as default and switch to +SQL only inside the template strings. This works in Vim with region-based +syntax rules and likely in many other editors. + + +### Block-specific Syntax Highlighting in Vim + +See [Different syntax highlighting within regions of a +file](http://vim.wikia.com/wiki/Different_syntax_highlighting_within_regions_of_a_file). + +```javascript +tpl.someQuery = new sqltt( /* @@sql@@ */ $=>$` + -- Your query here +` /* @@/sql@@ */); +``` + + +--- + +## Executing from CLI + +When a template file exports multiple queries and is invoked with no arguments, +SQLTT lists the available queries: + +**`$ node personnel.sql.js`** + +```sh +Available queries: + ✓ list: (Undocumented) + ✓ listByDept: (Undocumented) + ✓ show: (Undocumented) + ✓ insert: (Undocumented) + ✓ update: (Undocumented) +``` + +Select a query by name: + +**`$ node personnel.sql.js list`** + +```sql + select id, dptId, dptName, name, sex + from personnel + join depts using(dptId) +``` + +Pipe it directly to your database CLI: + +**`$ node personnel.sql.js list | psql tiaDB`** + +``` + id | dptid | dptname | name | sex +----+------------+----------------+-----------+----- + 1 | management | Management | Super | m + 3 | oper | Operations | Filemon | m + 2 | oper | Operations | Mortadelo | m +(3 rows) +``` + +Queries that require arguments accept them as additional positional parameters: + +**`$ node personnel.sql.js listByDept oper`** + +```sql +\set dptId '''oper''' + select id, dptId, dptName, name, sex + from personnel + join depts using(dptId) + where dptId = :dptId +``` + +**`$ node personnel.sql.js listByDept oper | psql tiaDB`** + +``` + id | dptid | dptname | name | sex +----+------------+----------------+-----------+----- + 3 | oper | Operations | Filemon | m + 2 | oper | Operations | Mortadelo | m +(2 rows) +``` + + +### Types and Quoting + +From the CLI all arguments are strings. Since SQLTT cannot know whether a value +is intended to be a number or a string, it quotes everything — most database +engines cast quoted values to numbers automatically when required. + + +--- + +## Using from a Node.js Application + +### Single Template + +```javascript +const myQuery = require('path/to/myTemplate.sql.js'); + +const sql = myQuery.sql('postgresql'); +const args = myQuery.args({ + arg1: "val1", + arg2: "val2", + /* ... */ +}); + +// db.query(sql, args); +``` + +> 📌 The `'postgresql'` argument selects the PostgreSQL SQL flavour. +> Templates can also specify a `default_engine` option to change the default. + + +### Multiple Templates + +```javascript +const myQueries = require('path/to/myTemplate.sql.js'); + +const userList_sql = myQueries.userList.sql('postgresql'); +const userProfile_sql = myQueries.userProfile.sql('postgresql'); + +const userProfile_args = myQueries.userProfile.args({ + userId: "someId", + /* ... */ +}); + +// db.query(userList_sql, []); +// db.query(userProfile_sql, userProfile_args); +``` + + +--- + +## Using from Non-JavaScript Languages + +Although SQLTT templates are JavaScript files, non-JS applications can still +benefit from them via the CLI and a `*_nocli` engine that renders standard SQL +without CLI-specific `\set` or `DEFINE` declarations. + +**`$ node --engine=postgresql_nocli personnel.sql.js listByDept oper`** + +```sql + select id, dptId, dptName, name, sex + from personnel + join depts using(dptId) + where dptId = $1 +``` + +A simple shell compilation script: + +```sh +#!/bin/env sh +export SQL_ENGINE=postgresql_nocli + +node sqlsrc/personnel.sql.js list > sql/personnel.list.sql +node sqlsrc/personnel.sql.js listByDept > sql/personnel.listByDept.sql +node sqlsrc/personnel.sql.js show > sql/personnel.show.sql +node sqlsrc/personnel.sql.js insert > sql/personnel.insert.sql +node sqlsrc/personnel.sql.js update > sql/personnel.update.sql + +node sqlsrc/articles.sql.js list > sql/articles.list.sql +node sqlsrc/articles.sql.js find > sql/articles.find.sql +# .... +``` diff --git a/docs/TEMPLATE_FORMAT.md b/docs/TEMPLATE_FORMAT.md new file mode 100644 index 0000000..2fcc65d --- /dev/null +++ b/docs/TEMPLATE_FORMAT.md @@ -0,0 +1,146 @@ +# SQLTT — Template Format + +--- + +## Table of Contents + +- [Overview](#overview) +- [Source Formats](#source-formats) + - [Simple String](#simple-string) + - [SQL Callback](#sql-callback) + - [Full Object](#full-object) +- [Template Properties](#template-properties) + - [name](#name) + - [description](#description) + - [sql](#sql) + - [args](#args) + - [alias](#alias) + - [altsql](#altsql) + - [data](#data) + - [with](#with) + +--- + +## Overview + +A SQLTT template is created by calling `new sqltt(source)`, where `source` can +be a plain string, an arrow-function SQL callback, or a full object. + +--- + +## Source Formats + +### Simple String + +The most concise form — no argument interpolation possible: + +```javascript +const q = new sqltt("select id, name from users"); +``` + +### SQL Callback + +An arrow function that receives the tag function `$` and returns a tagged +template literal: + +```javascript +const q = new sqltt($=>$` + select id, name + from users + where id = ${"userId"} +`); +``` + +The tag function `$` carries all [Tag API](API.md#tag-api) methods. The simple +`${"argName"}` interpolation is a shorthand for `${$.arg("argName")}`. + +### Full Object + +When you need to specify additional template properties beyond the SQL itself: + +```javascript +const q = new sqltt({ + name: "getUser", + description: "Fetch a single user by ID", + args: ["userId"], + sql: $=>$` + select id, name + from users + where id = ${"userId"} + `, +}); +``` + +--- + +## Template Properties + +### name + +*(Optional)* An identifier for the query. Used as the default CTE alias when +the template is included or used in a `WITH` clause, and in debug output. + +### description + +*(Optional)* A human-readable description, shown in CLI query listings. + +### sql + +*(Mandatory)* Either a plain SQL string or a [SQL Callback](#sql-callback). + +### args + +*(Optional)* An array of argument names that fixes the order in which +positional parameters (`$1`, `$2`, …) are numbered. + +If omitted or only partially specified, remaining arguments are numbered in +their order of first appearance in the SQL. + +**Example:** + +```javascript +{ + args: ["baz"], + sql: $=>$`select foo from bar where baz = ${"baz"} and qux = ${"qux"}` + // baz → $1, qux → $2 +} +``` + +### alias + +*(Optional)* An alias to use when this query is included alongside others via +`$.include([subq1, subq2, …])`. + +### altsql + +*(Optional)* Engine-specific SQL overrides. Useful when a query cannot be +written identically for all databases and a single generic version is not +feasible. + +```javascript +{ + sql: $=>$`/* Standard SQL */`, + altsql: { + oracle: $=>$`/* Oracle-specific SQL */`, + }, +} +``` + +> 📌 Argument names and their order are validated to be the same across all +> alternatives. Using the `args` property to fix the order is strongly +> recommended. + +### data + +*(Implemented — see [Advanced Features: Mutable Queries](ADVANCED.md#mutable-queries))* + +An object of static data accessible inside the template via `$.data()`. Allows +parameterising parts of a query (column lists, filter sets, etc.) without +re-writing the SQL. + +### with + +*(Implemented — see [Advanced Features: CTEs](ADVANCED.md#ctes))* + +An object of `{ alias: sqlttInstance }` pairs to declare Common Table +Expressions (CTEs / `WITH` clauses) that this query depends on. diff --git a/docs/USAGE.md b/docs/USAGE.md new file mode 100644 index 0000000..dc3351b --- /dev/null +++ b/docs/USAGE.md @@ -0,0 +1,189 @@ +# SQLTT — Usage Guide + +--- + +## Table of Contents + +- [Installation](#installation) +- [Creating a Template](#creating-a-template) + - [Accepted Options](#accepted-options) +- [Writing Template Files](#writing-template-files) +- [Using Templates](#using-templates) + - [From a Node.js Application](#from-a-nodejs-application) + - [From the CLI](#from-the-cli) + - [Providing Arguments](#providing-arguments) + - [Executing Queries Directly](#executing-queries-directly) + - [Selecting an Engine Flavour](#selecting-an-engine-flavour) + - [Query Output Inspection](#query-output-inspection) + +--- + +## Installation + +```sh +npm install --save sqltt +``` + +--- + +## Creating a Template + +```javascript +const sqltt = require("sqltt"); + +const q = new sqltt(source, options); +``` + +- **`source`** — defines the template (see [Template Format](TEMPLATE_FORMAT.md)). +- **`options`** — optional object of behavioural modifiers (see below). + + +### Accepted Options + +| Option | Default | Description | +|:-------------------|:---------|:------------| +| `default_engine` | — | Change the default rendering engine for this template. | +| `check_arguments` | `true` | When `false`, argument validation errors auto-correct instead of throwing. | +| `debug` | `false` | Log every `.args()` call. Valid values: `true`, `'verbose'`, `'cli'`. | + +--- + +## Writing Template Files + +A template file may export one or many SQLTT instances. The recommended pattern +is to use `sqltt.publish()`, which also enables direct CLI execution. + +**Single template:** + +```javascript +const sqltt = require("sqltt"); + +const tpl = new sqltt( + /* Template source in any valid format */ +); + +sqltt.publish(module, tpl); +``` + +**Multiple templates:** + +```javascript +const sqltt = require("sqltt"); + +const tpl = { + aQuery: new sqltt( /* Template source… */ ), + anotherQuery: new sqltt( /* Template source… */ ), + /* … */ +}; + +sqltt.publish(module, tpl); +``` + +See [Template Format](TEMPLATE_FORMAT.md) for the full source syntax. + +--- + +## Using Templates + +`sqltt.publish(module, tpl)` replaces `module.exports = tpl` and is roughly +equivalent to: + +```javascript +module.exports = tpl; +module.parent || console.log(tpl.sql('cli')); +``` + +…with additional handling for multi-template files, argument `\set` / `DEFINE` +declarations, and other CLI nuances. + +This lets you use your templates in two ways: + +1. **From a Node.js application** — require the file and call `.sql()` / `.args()`. +2. **From the CLI** — execute the file with `node` and pipe the output to your + database interpreter. + + +### From a Node.js Application + +**Single template:** + +```javascript +const myQuery = require('path/to/myTemplate.sql.js'); + +const sql = myQuery.sql('postgresql'); +const args = myQuery.args({ + arg1: "val1", + arg2: "val2", +}); + +// db.query(sql, args); +``` + +**Multiple templates:** + +```javascript +const myQueries = require('path/to/myTemplate.sql.js'); + +const sql = myQueries.someQuery.sql('postgresql'); +const args = myQueries.someQuery.args({ /* … */ }); + +// db.query(sql, args); +``` + + +### From the CLI + +```sh +node path/to/myTemplate.sql.js +``` + +- **Single-template file** — renders its SQL directly. +- **Multi-template file with no arguments** — lists available query names. +- **Multi-template file with a query name** — renders that query's SQL. + + +#### Providing Arguments + +Pass arguments as additional positional parameters: + +```sh +node personnel.sql.js show 23 +``` + +```sql +\set id '''23''' + select * + from personnel + where id = :id +``` + +> 📌 All CLI arguments are quoted unconditionally because SQLTT cannot +> determine whether a value is meant to be a number or a string. Most databases +> cast quoted numeric values automatically. + + +#### Executing Queries Directly + +Pipe the output to your database CLI interpreter: + +```sh +node personnel.sql.js list | psql tiaDB +``` + + +#### Selecting an Engine Flavour + +Use the `SQL_ENGINE` environment variable or the `--engine` flag. See +[Engines](ENGINES.md) for full details. + + +#### Query Output Inspection + +Use a `*_nocli` engine to render standard SQL without CLI variable declarations +(useful for inspecting how a query will look when called from your application): + +```sh +SQL_ENGINE=postgresql_nocli node myTpl.sql.js myQuery +# or +node myTpl.sql.js myQuery --postgresql_nocli +```