├── .commitlintrc.json ├── .eslintrc ├── .gitattributes ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .huskyrc ├── .metadocrc ├── .prettierrc ├── .remarkrc ├── AUTHORS ├── LICENSE ├── doc └── sql.md ├── lib ├── conditions-builder.js ├── delete-builder.js ├── insert-builder.js ├── params-builder.js ├── pg-insert-builder.js ├── pg-params-builder.js ├── pg-select-builder.js ├── pg.js ├── query-builder.js ├── query-conditions-builder.js ├── select-builder.js ├── update-builder.js └── utils.js ├── package-lock.json ├── package.json ├── sql.js ├── test ├── conditions-builder.js ├── delete-builder.js ├── insert-builder.js ├── pg-insert-builder.js ├── pg-select-builder.js ├── sql.js ├── sqlgen.js └── update-builder.js └── types ├── lib ├── conditions-builder.d.ts ├── delete-builder.d.ts ├── insert-builder.d.ts ├── params-builder.d.ts ├── pg-insert-builder.d.ts ├── pg-params-builder.d.ts ├── pg-select-builder.d.ts ├── pg.d.ts ├── query-builder.d.ts ├── query-conditions-builder.d.ts ├── select-builder.d.ts ├── update-builder.d.ts └── utils.d.ts ├── sql.d.ts ├── test ├── conditions-builder.ts └── select-builder.ts └── tsconfig.json /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["metarhia", "plugin:prettier/recommended"], 3 | "rules": { 4 | "class-methods-use-this": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * -text 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Testing CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | 13 | strategy: 14 | matrix: 15 | node: 16 | - 16 17 | - 18 18 | - 20 19 | - 22 20 | os: 21 | - ubuntu-latest 22 | - windows-latest 23 | - macos-latest 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Use Node.js ${{ matrix.node }} 28 | uses: actions/setup-node@v4 29 | with: 30 | node-version: ${{ matrix.node }} 31 | - uses: actions/cache@v4 32 | with: 33 | path: ~/.npm 34 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 35 | restore-keys: | 36 | ${{ runner.os }}-node- 37 | - run: npm ci 38 | - run: npm test 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/node,intellij+all 2 | # Edit at https://www.gitignore.io/?templates=node,intellij+all 3 | 4 | ### Intellij+all ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # CMake 9 | cmake-build-*/ 10 | 11 | # IntelliJ 12 | out/ 13 | 14 | .idea/ 15 | 16 | *.iml 17 | modules.xml 18 | 19 | ### Node ### 20 | # Logs 21 | logs 22 | *.log 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # Runtime data 28 | pids 29 | *.pid 30 | *.seed 31 | *.pid.lock 32 | 33 | # Directory for instrumented libs generated by jscoverage/JSCover 34 | lib-cov 35 | 36 | # Coverage directory used by tools like istanbul 37 | coverage 38 | 39 | # nyc test coverage 40 | .nyc_output 41 | 42 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | bower_components 47 | 48 | # node-waf configuration 49 | .lock-wscript 50 | 51 | # Compiled binary addons (https://nodejs.org/api/addons.html) 52 | build/Release 53 | 54 | # Dependency directories 55 | node_modules/ 56 | jspm_packages/ 57 | 58 | # TypeScript v1 declaration files 59 | typings/ 60 | 61 | # Optional npm cache directory 62 | .npm 63 | 64 | # Optional eslint cache 65 | .eslintcache 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | 83 | # next.js build output 84 | .next 85 | 86 | # nuxt.js build output 87 | .nuxt 88 | 89 | # vuepress build output 90 | .vuepress/dist 91 | 92 | # Serverless directories 93 | .serverless/ 94 | 95 | # FuseBox cache 96 | .fusebox/ 97 | 98 | # DynamoDB Local files 99 | .dynamodb/ 100 | 101 | # End of https://www.gitignore.io/api/node,intellij+all 102 | -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "npm test", 4 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.metadocrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": ["sql.js"], 3 | "minHeaderLevel": 2, 4 | "header": "# API Documentation\n", 5 | "outputDir": "doc/" 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "overrides": [ 5 | { 6 | "files": ["**/.*rc"], 7 | "options": { "parser": "json" } 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.remarkrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "remark-preset-lint-metarhia", 4 | "remark-validate-links", 5 | ["remark-lint-emphasis-marker", "_"] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Denys Otrishko 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2020 Metarhia contributors (full list in AUTHORS file) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /doc/sql.md: -------------------------------------------------------------------------------- 1 | # API Documentation 2 | 3 | - [Interface sql](#interface-sql) 4 | - [QueryBuilder](#class-querybuilder) 5 | - [QueryBuilder.prototype.constructor](#querybuilderprototypeconstructorparams-options) 6 | - [QueryBuilder.prototype.build](#querybuilderprototypebuild) 7 | - [QueryBuilder.prototype.buildParams](#querybuilderprototypebuildparams) 8 | - [QueryBuilder.prototype.key](#querybuilderprototypekeykey) 9 | - [QueryBuilder.prototype.makeKeyOrExpr](#querybuilderprototypemakekeyorexprvalue-wrap--false) 10 | - [QueryBuilder.prototype.makeParamValue](#querybuilderprototypemakeparamvaluevalue) 11 | - [QueryBuilder.prototype.nested](#querybuilderprototypenested) 12 | - [QueryBuilder.prototype.raw](#querybuilderprototyperawsqltemplate) 13 | - [QueryConditionsBuilder](#class-queryconditionsbuilder-extends-querybuilder) 14 | - [QueryConditionsBuilder.prototype.constructor](#queryconditionsbuilderprototypeconstructorparams-options) 15 | - [QueryConditionsBuilder.prototype.\_whereValueMapper](#queryconditionsbuilderprototype_wherevaluemappervalue) 16 | - [QueryConditionsBuilder.prototype.orWhere](#queryconditionsbuilderprototypeorwherekey-cond-value) 17 | - [QueryConditionsBuilder.prototype.orWhereAny](#queryconditionsbuilderprototypeorwhereanykey-value) 18 | - [QueryConditionsBuilder.prototype.orWhereBetween](#queryconditionsbuilderprototypeorwherebetweenkey-from-to-symmetric) 19 | - [QueryConditionsBuilder.prototype.orWhereEq](#queryconditionsbuilderprototypeorwhereeqkey-value) 20 | - [QueryConditionsBuilder.prototype.orWhereExists](#queryconditionsbuilderprototypeorwhereexistssubquery) 21 | - [QueryConditionsBuilder.prototype.orWhereILike](#queryconditionsbuilderprototypeorwhereilikekey-value) 22 | - [QueryConditionsBuilder.prototype.orWhereIn](#queryconditionsbuilderprototypeorwhereinkey-conds) 23 | - [QueryConditionsBuilder.prototype.orWhereKey](#queryconditionsbuilderprototypeorwherekeyleftkey-cond-rightkey) 24 | - [QueryConditionsBuilder.prototype.orWhereLess](#queryconditionsbuilderprototypeorwherelesskey-value) 25 | - [QueryConditionsBuilder.prototype.orWhereLessEq](#queryconditionsbuilderprototypeorwherelesseqkey-value) 26 | - [QueryConditionsBuilder.prototype.orWhereLike](#queryconditionsbuilderprototypeorwherelikekey-value) 27 | - [QueryConditionsBuilder.prototype.orWhereMore](#queryconditionsbuilderprototypeorwheremorekey-value) 28 | - [QueryConditionsBuilder.prototype.orWhereMoreEq](#queryconditionsbuilderprototypeorwheremoreeqkey-value) 29 | - [QueryConditionsBuilder.prototype.orWhereNot](#queryconditionsbuilderprototypeorwherenotkey-cond-value) 30 | - [QueryConditionsBuilder.prototype.orWhereNotBetween](#queryconditionsbuilderprototypeorwherenotbetweenkey-from-to-symmetric) 31 | - [QueryConditionsBuilder.prototype.orWhereNotILike](#queryconditionsbuilderprototypeorwherenotilikekey-value) 32 | - [QueryConditionsBuilder.prototype.orWhereNotIn](#queryconditionsbuilderprototypeorwherenotinkey-conds) 33 | - [QueryConditionsBuilder.prototype.orWhereNotKey](#queryconditionsbuilderprototypeorwherenotkeyleftkey-cond-rightkey) 34 | - [QueryConditionsBuilder.prototype.orWhereNotLike](#queryconditionsbuilderprototypeorwherenotlikekey-value) 35 | - [QueryConditionsBuilder.prototype.orWhereNotNull](#queryconditionsbuilderprototypeorwherenotnullkey) 36 | - [QueryConditionsBuilder.prototype.orWhereNotRaw](#queryconditionsbuilderprototypeorwherenotrawsql) 37 | - [QueryConditionsBuilder.prototype.orWhereNull](#queryconditionsbuilderprototypeorwherenullkey) 38 | - [QueryConditionsBuilder.prototype.orWhereRaw](#queryconditionsbuilderprototypeorwhererawsql) 39 | - [QueryConditionsBuilder.prototype.where](#queryconditionsbuilderprototypewherekey-cond-value) 40 | - [QueryConditionsBuilder.prototype.whereAny](#queryconditionsbuilderprototypewhereanykey-value) 41 | - [QueryConditionsBuilder.prototype.whereBetween](#queryconditionsbuilderprototypewherebetweenkey-from-to-symmetric) 42 | - [QueryConditionsBuilder.prototype.whereEq](#queryconditionsbuilderprototypewhereeqkey-value) 43 | - [QueryConditionsBuilder.prototype.whereExists](#queryconditionsbuilderprototypewhereexistssubquery) 44 | - [QueryConditionsBuilder.prototype.whereILike](#queryconditionsbuilderprototypewhereilikekey-value) 45 | - [QueryConditionsBuilder.prototype.whereIn](#queryconditionsbuilderprototypewhereinkey-conds) 46 | - [QueryConditionsBuilder.prototype.whereKey](#queryconditionsbuilderprototypewherekeyleftkey-cond-rightkey) 47 | - [QueryConditionsBuilder.prototype.whereLess](#queryconditionsbuilderprototypewherelesskey-value) 48 | - [QueryConditionsBuilder.prototype.whereLessEq](#queryconditionsbuilderprototypewherelesseqkey-value) 49 | - [QueryConditionsBuilder.prototype.whereLike](#queryconditionsbuilderprototypewherelikekey-value) 50 | - [QueryConditionsBuilder.prototype.whereMore](#queryconditionsbuilderprototypewheremorekey-value) 51 | - [QueryConditionsBuilder.prototype.whereMoreEq](#queryconditionsbuilderprototypewheremoreeqkey-value) 52 | - [QueryConditionsBuilder.prototype.whereNot](#queryconditionsbuilderprototypewherenotkey-cond-value) 53 | - [QueryConditionsBuilder.prototype.whereNotBetween](#queryconditionsbuilderprototypewherenotbetweenkey-from-to-symmetric) 54 | - [QueryConditionsBuilder.prototype.whereNotILike](#queryconditionsbuilderprototypewherenotilikekey-value) 55 | - [QueryConditionsBuilder.prototype.whereNotIn](#queryconditionsbuilderprototypewherenotinkey-conds) 56 | - [QueryConditionsBuilder.prototype.whereNotKey](#queryconditionsbuilderprototypewherenotkeyleftkey-cond-rightkey) 57 | - [QueryConditionsBuilder.prototype.whereNotLike](#queryconditionsbuilderprototypewherenotlikekey-value) 58 | - [QueryConditionsBuilder.prototype.whereNotNull](#queryconditionsbuilderprototypewherenotnullkey) 59 | - [QueryConditionsBuilder.prototype.whereNotRaw](#queryconditionsbuilderprototypewherenotrawsql) 60 | - [QueryConditionsBuilder.prototype.whereNull](#queryconditionsbuilderprototypewherenullkey) 61 | - [QueryConditionsBuilder.prototype.whereRaw](#queryconditionsbuilderprototypewhererawsql) 62 | - [SelectBuilder](#class-selectbuilder-extends-queryconditionsbuilder) 63 | - [SelectBuilder.prototype.constructor](#selectbuilderprototypeconstructorparams-options) 64 | - [SelectBuilder.prototype.\_addSelectClause](#selectbuilderprototype_addselectclausetype-field-alias) 65 | - [SelectBuilder.prototype.avg](#selectbuilderprototypeavgfield-alias) 66 | - [SelectBuilder.prototype.build](#selectbuilderprototypebuild) 67 | - [SelectBuilder.prototype.count](#selectbuilderprototypecountfield---alias) 68 | - [SelectBuilder.prototype.countOver](#selectbuilderprototypecountoverfield---alias) 69 | - [SelectBuilder.prototype.crossJoin](#selectbuilderprototypecrossjointablename) 70 | - [SelectBuilder.prototype.crossJoinAs](#selectbuilderprototypecrossjoinastablename-alias) 71 | - [SelectBuilder.prototype.distinct](#selectbuilderprototypedistinct) 72 | - [SelectBuilder.prototype.from](#selectbuilderprototypefromtablename-alias) 73 | - [SelectBuilder.prototype.fullJoin](#selectbuilderprototypefulljointablename-leftkey-rightkey) 74 | - [SelectBuilder.prototype.fullJoinAs](#selectbuilderprototypefulljoinastablename-alias-leftkey-rightkey) 75 | - [SelectBuilder.prototype.fullJoinCond](#selectbuilderprototypefulljoincondtablename-condition) 76 | - [SelectBuilder.prototype.fullJoinCondAs](#selectbuilderprototypefulljoincondastablename-alias-condition) 77 | - [SelectBuilder.prototype.groupBy](#selectbuilderprototypegroupbyfields) 78 | - [SelectBuilder.prototype.groupByRaw](#selectbuilderprototypegroupbyrawraw) 79 | - [SelectBuilder.prototype.innerJoin](#selectbuilderprototypeinnerjointablename-leftkey-rightkey) 80 | - [SelectBuilder.prototype.innerJoinAs](#selectbuilderprototypeinnerjoinastablename-alias-leftkey-rightkey) 81 | - [SelectBuilder.prototype.innerJoinCond](#selectbuilderprototypeinnerjoincondtablename-condition) 82 | - [SelectBuilder.prototype.innerJoinCondAs](#selectbuilderprototypeinnerjoincondastablename-alias-condition) 83 | - [SelectBuilder.prototype.join](#selectbuilderprototypejoinkind-tablename-alias-leftkey-rightkey) 84 | - [SelectBuilder.prototype.joinCond](#selectbuilderprototypejoincondkind-tablename-alias-condition) 85 | - [SelectBuilder.prototype.leftJoin](#selectbuilderprototypeleftjointablename-leftkey-rightkey) 86 | - [SelectBuilder.prototype.leftJoinAs](#selectbuilderprototypeleftjoinastablename-alias-leftkey-rightkey) 87 | - [SelectBuilder.prototype.leftJoinCond](#selectbuilderprototypeleftjoincondtablename-condition) 88 | - [SelectBuilder.prototype.leftJoinCondAs](#selectbuilderprototypeleftjoincondastablename-alias-condition) 89 | - [SelectBuilder.prototype.limit](#selectbuilderprototypelimitlimit) 90 | - [SelectBuilder.prototype.max](#selectbuilderprototypemaxfield-alias) 91 | - [SelectBuilder.prototype.min](#selectbuilderprototypeminfield-alias) 92 | - [SelectBuilder.prototype.naturalJoin](#selectbuilderprototypenaturaljointablename) 93 | - [SelectBuilder.prototype.naturalJoinAs](#selectbuilderprototypenaturaljoinastablename-alias) 94 | - [SelectBuilder.prototype.offset](#selectbuilderprototypeoffsetoffset) 95 | - [SelectBuilder.prototype.orderBy](#selectbuilderprototypeorderbyfield-dir--asc) 96 | - [SelectBuilder.prototype.orderByRaw](#selectbuilderprototypeorderbyrawraw) 97 | - [SelectBuilder.prototype.rightJoin](#selectbuilderprototyperightjointablename-leftkey-rightkey) 98 | - [SelectBuilder.prototype.rightJoinAs](#selectbuilderprototyperightjoinastablename-alias-leftkey-rightkey) 99 | - [SelectBuilder.prototype.rightJoinCond](#selectbuilderprototyperightjoincondtablename-condition) 100 | - [SelectBuilder.prototype.rightJoinCondAs](#selectbuilderprototyperightjoincondastablename-alias-condition) 101 | - [SelectBuilder.prototype.select](#selectbuilderprototypeselectfields) 102 | - [SelectBuilder.prototype.selectAs](#selectbuilderprototypeselectasfieldorbuilder-alias) 103 | - [SelectBuilder.prototype.selectFn](#selectbuilderprototypeselectfnfn-field-alias) 104 | - [SelectBuilder.prototype.selectRaw](#selectbuilderprototypeselectrawsqlorbuilder) 105 | - [SelectBuilder.prototype.sum](#selectbuilderprototypesumfield-alias) 106 | - [SelectBuilder.prototype.with](#selectbuilderprototypewithalias-sql) 107 | - [RawBuilder](#class-rawbuilder-extends-querybuilder) 108 | - [RawBuilder.prototype.constructor](#rawbuilderprototypeconstructorsqltemplate) 109 | - [RawBuilder.prototype.build](#rawbuilderprototypebuild) 110 | - [UpdateBuilder](#class-updatebuilder-extends-queryconditionsbuilder) 111 | - [UpdateBuilder.prototype.constructor](#updatebuilderprototypeconstructorparams-options) 112 | - [UpdateBuilder.prototype.build](#updatebuilderprototypebuild) 113 | - [UpdateBuilder.prototype.from](#updatebuilderprototypefromtablename-alias) 114 | - [UpdateBuilder.prototype.returning](#updatebuilderprototypereturningkey-alias) 115 | - [UpdateBuilder.prototype.select](#updatebuilderprototypeselect) 116 | - [UpdateBuilder.prototype.set](#updatebuilderprototypesetcolumn-value) 117 | - [UpdateBuilder.prototype.setKey](#updatebuilderprototypesetkeycolumn-key) 118 | - [UpdateBuilder.prototype.sets](#updatebuilderprototypesetsobj) 119 | - [UpdateBuilder.prototype.table](#updatebuilderprototypetabletablename-alias) 120 | - [UpdateBuilder.prototype.with](#updatebuilderprototypewithalias-sql) 121 | - [DeleteBuilder](#class-deletebuilder-extends-queryconditionsbuilder) 122 | - [DeleteBuilder.prototype.constructor](#deletebuilderprototypeconstructorparams-options) 123 | - [DeleteBuilder.prototype.build](#deletebuilderprototypebuild) 124 | - [DeleteBuilder.prototype.from](#deletebuilderprototypefromtablename-alias) 125 | - [InsertBuilder](#class-insertbuilder-extends-querybuilder) 126 | - [InsertBuilder.prototype.constructor](#insertbuilderprototypeconstructorparams-options) 127 | - [InsertBuilder.prototype.build](#insertbuilderprototypebuild) 128 | - [InsertBuilder.prototype.from](#insertbuilderprototypefromsql) 129 | - [InsertBuilder.prototype.select](#insertbuilderprototypeselect) 130 | - [InsertBuilder.prototype.table](#insertbuilderprototypetabletablename-alias) 131 | - [InsertBuilder.prototype.value](#insertbuilderprototypevaluekey-value) 132 | - [InsertBuilder.prototype.values](#insertbuilderprototypevaluesobj) 133 | - [InsertBuilder.prototype.with](#insertbuilderprototypewithalias-sql) 134 | - [PgInsertBuilder](#class-pginsertbuilder-extends-insertbuilder) 135 | - [PgInsertBuilder.prototype.constructor](#pginsertbuilderprototypeconstructorparams-options) 136 | - [PgInsertBuilder.prototype.build](#pginsertbuilderprototypebuild) 137 | - [PgInsertBuilder.prototype.conflict](#pginsertbuilderprototypeconflicttarget-action) 138 | - [PgInsertBuilder.prototype.returning](#pginsertbuilderprototypereturningkey-alias) 139 | - [PgSelectBuilder](#class-pgselectbuilder-extends-selectbuilder) 140 | - [PgSelectBuilder.prototype.constructor](#pgselectbuilderprototypeconstructorparams-options) 141 | - [PgSelectBuilder.prototype.distinctOn](#pgselectbuilderprototypedistinctonkeyorexpr) 142 | - [ConditionsBuilder](#class-conditionsbuilder-extends-querybuilder) 143 | - [ConditionsBuilder.prototype.constructor](#conditionsbuilderprototypeconstructorparams-options) 144 | - [ConditionsBuilder.prototype.and](#conditionsbuilderprototypeandkey-cond-value) 145 | - [ConditionsBuilder.prototype.andConds](#conditionsbuilderprototypeandcondsconditions) 146 | - [ConditionsBuilder.prototype.andKey](#conditionsbuilderprototypeandkeyleftkey-cond-rightkey) 147 | - [ConditionsBuilder.prototype.andRaw](#conditionsbuilderprototypeandrawsql) 148 | - [ConditionsBuilder.prototype.any](#conditionsbuilderprototypeanykey-value) 149 | - [ConditionsBuilder.prototype.between](#conditionsbuilderprototypebetweenkey-from-to-symmetric--false) 150 | - [ConditionsBuilder.prototype.build](#conditionsbuilderprototypebuild) 151 | - [ConditionsBuilder.prototype.exists](#conditionsbuilderprototypeexistssubquery) 152 | - [ConditionsBuilder.prototype.ilike](#conditionsbuilderprototypeilikekey-value) 153 | - [ConditionsBuilder.prototype.in](#conditionsbuilderprototypeinkey-conds) 154 | - [ConditionsBuilder.prototype.like](#conditionsbuilderprototypelikekey-value) 155 | - [ConditionsBuilder.prototype.not](#conditionsbuilderprototypenotkey-cond-value) 156 | - [ConditionsBuilder.prototype.notBetween](#conditionsbuilderprototypenotbetweenkey-from-to-symmetric--false) 157 | - [ConditionsBuilder.prototype.notILike](#conditionsbuilderprototypenotilikekey-value) 158 | - [ConditionsBuilder.prototype.notIn](#conditionsbuilderprototypenotinkey-conds) 159 | - [ConditionsBuilder.prototype.notKey](#conditionsbuilderprototypenotkeyleftkey-cond-rightkey) 160 | - [ConditionsBuilder.prototype.notLike](#conditionsbuilderprototypenotlikekey-value) 161 | - [ConditionsBuilder.prototype.notNull](#conditionsbuilderprototypenotnullkey) 162 | - [ConditionsBuilder.prototype.notRaw](#conditionsbuilderprototypenotrawsql) 163 | - [ConditionsBuilder.prototype.null](#conditionsbuilderprototypenullkey) 164 | - [ConditionsBuilder.prototype.or](#conditionsbuilderprototypeorkey-cond-value) 165 | - [ConditionsBuilder.prototype.orAny](#conditionsbuilderprototypeoranykey-value) 166 | - [ConditionsBuilder.prototype.orBetween](#conditionsbuilderprototypeorbetweenkey-from-to-symmetric--false) 167 | - [ConditionsBuilder.prototype.orConds](#conditionsbuilderprototypeorcondsconditions) 168 | - [ConditionsBuilder.prototype.orExists](#conditionsbuilderprototypeorexistssubquery) 169 | - [ConditionsBuilder.prototype.orILike](#conditionsbuilderprototypeorilikekey-value) 170 | - [ConditionsBuilder.prototype.orIn](#conditionsbuilderprototypeorinkey-conds) 171 | - [ConditionsBuilder.prototype.orKey](#conditionsbuilderprototypeorkeyleftkey-cond-rightkey) 172 | - [ConditionsBuilder.prototype.orLike](#conditionsbuilderprototypeorlikekey-value) 173 | - [ConditionsBuilder.prototype.orNot](#conditionsbuilderprototypeornotkey-cond-value) 174 | - [ConditionsBuilder.prototype.orNotBetween](#conditionsbuilderprototypeornotbetweenkey-from-to-symmetric--false) 175 | - [ConditionsBuilder.prototype.orNotILike](#conditionsbuilderprototypeornotilikekey-value) 176 | - [ConditionsBuilder.prototype.orNotIn](#conditionsbuilderprototypeornotinkey-conds) 177 | - [ConditionsBuilder.prototype.orNotKey](#conditionsbuilderprototypeornotkeyleftkey-cond-rightkey) 178 | - [ConditionsBuilder.prototype.orNotLike](#conditionsbuilderprototypeornotlikekey-value) 179 | - [ConditionsBuilder.prototype.orNotNull](#conditionsbuilderprototypeornotnullkey) 180 | - [ConditionsBuilder.prototype.orNotRaw](#conditionsbuilderprototypeornotrawsql) 181 | - [ConditionsBuilder.prototype.orNull](#conditionsbuilderprototypeornullkey) 182 | - [ConditionsBuilder.prototype.orRaw](#conditionsbuilderprototypeorrawsql) 183 | - [ParamsBuilder](#class-paramsbuilder) 184 | - [ParamsBuilder.prototype.constructor](#paramsbuilderprototypeconstructor) 185 | - [ParamsBuilder.prototype.add](#paramsbuilderprototypeaddvalue-options) 186 | - [ParamsBuilder.prototype.build](#paramsbuilderprototypebuild) 187 | - [PostgresParamsBuilder](#class-postgresparamsbuilder-extends-paramsbuilder) 188 | - [PostgresParamsBuilder.prototype.constructor](#postgresparamsbuilderprototypeconstructor) 189 | - [PostgresParamsBuilder.prototype.add](#postgresparamsbuilderprototypeaddvalue-options) 190 | - [PostgresParamsBuilder.prototype.build](#postgresparamsbuilderprototypebuild) 191 | - [pg](#pgoptions) 192 | - [pgSelect](#pgselectoptions) 193 | - [pgInsert](#pginsertoptions) 194 | - [pgUpdate](#pgupdateoptions) 195 | - [pgDelete](#pgdeleteoptions) 196 | - [pgQuerySelect](#pgqueryselectpg-handler) 197 | - [pgQueryInsert](#pgqueryinsertpg-handler) 198 | - [pgQueryUpdate](#pgqueryupdatepg-handler) 199 | - [pgQueryDelete](#pgquerydeletepg-handler) 200 | 201 | ## Interface: sql 202 | 203 | ### class QueryBuilder 204 | 205 | #### QueryBuilder.prototype.constructor(params, options) 206 | 207 | - `params`: `` 208 | - `options`: [``][object] 209 | - `escapeIdentifier`: [``][function] 210 | - `identifier`: [``][string] to escape 211 | - _Returns:_ [``][string] escaped string 212 | 213 | #### QueryBuilder.prototype.build() 214 | 215 | _Returns:_ [``][string] 216 | 217 | Build and return the SQL query 218 | 219 | #### QueryBuilder.prototype.buildParams() 220 | 221 | _Returns:_ `>` 222 | 223 | Build params for this query 224 | 225 | #### QueryBuilder.prototype.key(key) 226 | 227 | #### QueryBuilder.prototype.makeKeyOrExpr(value, wrap = false) 228 | 229 | #### QueryBuilder.prototype.makeParamValue(value) 230 | 231 | #### QueryBuilder.prototype.nested() 232 | 233 | #### QueryBuilder.prototype.raw(sqlTemplate) 234 | 235 | ### class QueryConditionsBuilder extends [QueryBuilder][sql-querybuilder] 236 | 237 | #### QueryConditionsBuilder.prototype.constructor(params, options) 238 | 239 | #### QueryConditionsBuilder.prototype.\_whereValueMapper(value) 240 | 241 | #### QueryConditionsBuilder.prototype.orWhere(key, cond, value) 242 | 243 | #### QueryConditionsBuilder.prototype.orWhereAny(key, value) 244 | 245 | #### QueryConditionsBuilder.prototype.orWhereBetween(key, from, to, symmetric) 246 | 247 | #### QueryConditionsBuilder.prototype.orWhereEq(key, value) 248 | 249 | #### QueryConditionsBuilder.prototype.orWhereExists(subquery) 250 | 251 | #### QueryConditionsBuilder.prototype.orWhereILike(key, value) 252 | 253 | #### QueryConditionsBuilder.prototype.orWhereIn(key, conds) 254 | 255 | #### QueryConditionsBuilder.prototype.orWhereKey(leftKey, cond, rightKey) 256 | 257 | #### QueryConditionsBuilder.prototype.orWhereLess(key, value) 258 | 259 | #### QueryConditionsBuilder.prototype.orWhereLessEq(key, value) 260 | 261 | #### QueryConditionsBuilder.prototype.orWhereLike(key, value) 262 | 263 | #### QueryConditionsBuilder.prototype.orWhereMore(key, value) 264 | 265 | #### QueryConditionsBuilder.prototype.orWhereMoreEq(key, value) 266 | 267 | #### QueryConditionsBuilder.prototype.orWhereNot(key, cond, value) 268 | 269 | #### QueryConditionsBuilder.prototype.orWhereNotBetween(key, from, to, symmetric) 270 | 271 | #### QueryConditionsBuilder.prototype.orWhereNotILike(key, value) 272 | 273 | #### QueryConditionsBuilder.prototype.orWhereNotIn(key, conds) 274 | 275 | #### QueryConditionsBuilder.prototype.orWhereNotKey(leftKey, cond, rightKey) 276 | 277 | #### QueryConditionsBuilder.prototype.orWhereNotLike(key, value) 278 | 279 | #### QueryConditionsBuilder.prototype.orWhereNotNull(key) 280 | 281 | #### QueryConditionsBuilder.prototype.orWhereNotRaw(sql) 282 | 283 | #### QueryConditionsBuilder.prototype.orWhereNull(key) 284 | 285 | #### QueryConditionsBuilder.prototype.orWhereRaw(sql) 286 | 287 | #### QueryConditionsBuilder.prototype.where(key, cond, value) 288 | 289 | #### QueryConditionsBuilder.prototype.whereAny(key, value) 290 | 291 | #### QueryConditionsBuilder.prototype.whereBetween(key, from, to, symmetric) 292 | 293 | #### QueryConditionsBuilder.prototype.whereEq(key, value) 294 | 295 | #### QueryConditionsBuilder.prototype.whereExists(subquery) 296 | 297 | #### QueryConditionsBuilder.prototype.whereILike(key, value) 298 | 299 | #### QueryConditionsBuilder.prototype.whereIn(key, conds) 300 | 301 | #### QueryConditionsBuilder.prototype.whereKey(leftKey, cond, rightKey) 302 | 303 | #### QueryConditionsBuilder.prototype.whereLess(key, value) 304 | 305 | #### QueryConditionsBuilder.prototype.whereLessEq(key, value) 306 | 307 | #### QueryConditionsBuilder.prototype.whereLike(key, value) 308 | 309 | #### QueryConditionsBuilder.prototype.whereMore(key, value) 310 | 311 | #### QueryConditionsBuilder.prototype.whereMoreEq(key, value) 312 | 313 | #### QueryConditionsBuilder.prototype.whereNot(key, cond, value) 314 | 315 | #### QueryConditionsBuilder.prototype.whereNotBetween(key, from, to, symmetric) 316 | 317 | #### QueryConditionsBuilder.prototype.whereNotILike(key, value) 318 | 319 | #### QueryConditionsBuilder.prototype.whereNotIn(key, conds) 320 | 321 | #### QueryConditionsBuilder.prototype.whereNotKey(leftKey, cond, rightKey) 322 | 323 | #### QueryConditionsBuilder.prototype.whereNotLike(key, value) 324 | 325 | #### QueryConditionsBuilder.prototype.whereNotNull(key) 326 | 327 | #### QueryConditionsBuilder.prototype.whereNotRaw(sql) 328 | 329 | #### QueryConditionsBuilder.prototype.whereNull(key) 330 | 331 | #### QueryConditionsBuilder.prototype.whereRaw(sql) 332 | 333 | ### class SelectBuilder extends [QueryConditionsBuilder][sql-queryconditionsbuilder] 334 | 335 | #### SelectBuilder.prototype.constructor(params, options) 336 | 337 | #### SelectBuilder.prototype.\_addSelectClause(type, field, alias) 338 | 339 | #### SelectBuilder.prototype.avg(field, alias) 340 | 341 | #### SelectBuilder.prototype.build() 342 | 343 | #### SelectBuilder.prototype.count(field = '\*', alias) 344 | 345 | #### SelectBuilder.prototype.countOver(field = '\*', alias) 346 | 347 | #### SelectBuilder.prototype.crossJoin(tableName) 348 | 349 | #### SelectBuilder.prototype.crossJoinAs(tableName, alias) 350 | 351 | #### SelectBuilder.prototype.distinct() 352 | 353 | #### SelectBuilder.prototype.from(tableName, alias) 354 | 355 | #### SelectBuilder.prototype.fullJoin(tableName, leftKey, rightKey) 356 | 357 | #### SelectBuilder.prototype.fullJoinAs(tableName, alias, leftKey, rightKey) 358 | 359 | #### SelectBuilder.prototype.fullJoinCond(tableName, condition) 360 | 361 | #### SelectBuilder.prototype.fullJoinCondAs(tableName, alias, condition) 362 | 363 | #### SelectBuilder.prototype.groupBy(...fields) 364 | 365 | #### SelectBuilder.prototype.groupByRaw(raw) 366 | 367 | #### SelectBuilder.prototype.innerJoin(tableName, leftKey, rightKey) 368 | 369 | #### SelectBuilder.prototype.innerJoinAs(tableName, alias, leftKey, rightKey) 370 | 371 | #### SelectBuilder.prototype.innerJoinCond(tableName, condition) 372 | 373 | #### SelectBuilder.prototype.innerJoinCondAs(tableName, alias, condition) 374 | 375 | #### SelectBuilder.prototype.join(kind, tableName, alias, leftKey, rightKey) 376 | 377 | #### SelectBuilder.prototype.joinCond(kind, tableName, alias, condition) 378 | 379 | #### SelectBuilder.prototype.leftJoin(tableName, leftKey, rightKey) 380 | 381 | #### SelectBuilder.prototype.leftJoinAs(tableName, alias, leftKey, rightKey) 382 | 383 | #### SelectBuilder.prototype.leftJoinCond(tableName, condition) 384 | 385 | #### SelectBuilder.prototype.leftJoinCondAs(tableName, alias, condition) 386 | 387 | #### SelectBuilder.prototype.limit(limit) 388 | 389 | #### SelectBuilder.prototype.max(field, alias) 390 | 391 | #### SelectBuilder.prototype.min(field, alias) 392 | 393 | #### SelectBuilder.prototype.naturalJoin(tableName) 394 | 395 | #### SelectBuilder.prototype.naturalJoinAs(tableName, alias) 396 | 397 | #### SelectBuilder.prototype.offset(offset) 398 | 399 | #### SelectBuilder.prototype.orderBy(field, dir = 'ASC') 400 | 401 | #### SelectBuilder.prototype.orderByRaw(raw) 402 | 403 | #### SelectBuilder.prototype.rightJoin(tableName, leftKey, rightKey) 404 | 405 | #### SelectBuilder.prototype.rightJoinAs(tableName, alias, leftKey, rightKey) 406 | 407 | #### SelectBuilder.prototype.rightJoinCond(tableName, condition) 408 | 409 | #### SelectBuilder.prototype.rightJoinCondAs(tableName, alias, condition) 410 | 411 | #### SelectBuilder.prototype.select(...fields) 412 | 413 | #### SelectBuilder.prototype.selectAs(fieldOrBuilder, alias) 414 | 415 | #### SelectBuilder.prototype.selectFn(fn, field, alias) 416 | 417 | #### SelectBuilder.prototype.selectRaw(sqlOrBuilder) 418 | 419 | #### SelectBuilder.prototype.sum(field, alias) 420 | 421 | #### SelectBuilder.prototype.with(alias, sql) 422 | 423 | ### class RawBuilder extends [QueryBuilder][sql-querybuilder] 424 | 425 | #### RawBuilder.prototype.constructor(sqlTemplate) 426 | 427 | - `sqlTemplate`: [``][function] function or sql string 428 | - `params`: `` 429 | - _Returns:_ [``][string] query 430 | 431 | #### RawBuilder.prototype.build() 432 | 433 | ### class UpdateBuilder extends [QueryConditionsBuilder][sql-queryconditionsbuilder] 434 | 435 | #### UpdateBuilder.prototype.constructor(params, options) 436 | 437 | #### UpdateBuilder.prototype.build() 438 | 439 | #### UpdateBuilder.prototype.from(tableName, alias) 440 | 441 | #### UpdateBuilder.prototype.returning(key, alias) 442 | 443 | #### UpdateBuilder.prototype.select() 444 | 445 | #### UpdateBuilder.prototype.set(column, value) 446 | 447 | #### UpdateBuilder.prototype.setKey(column, key) 448 | 449 | #### UpdateBuilder.prototype.sets(obj) 450 | 451 | #### UpdateBuilder.prototype.table(tableName, alias) 452 | 453 | #### UpdateBuilder.prototype.with(alias, sql) 454 | 455 | ### class DeleteBuilder extends [QueryConditionsBuilder][sql-queryconditionsbuilder] 456 | 457 | #### DeleteBuilder.prototype.constructor(params, options) 458 | 459 | #### DeleteBuilder.prototype.build() 460 | 461 | #### DeleteBuilder.prototype.from(tableName, alias) 462 | 463 | ### class InsertBuilder extends [QueryBuilder][sql-querybuilder] 464 | 465 | #### InsertBuilder.prototype.constructor(params, options) 466 | 467 | #### InsertBuilder.prototype.build() 468 | 469 | #### InsertBuilder.prototype.from(sql) 470 | 471 | #### InsertBuilder.prototype.select() 472 | 473 | #### InsertBuilder.prototype.table(tableName, alias) 474 | 475 | #### InsertBuilder.prototype.value(key, value) 476 | 477 | #### InsertBuilder.prototype.values(obj) 478 | 479 | #### InsertBuilder.prototype.with(alias, sql) 480 | 481 | ### class PgInsertBuilder extends [InsertBuilder][sql-insertbuilder] 482 | 483 | #### PgInsertBuilder.prototype.constructor(params, options) 484 | 485 | #### PgInsertBuilder.prototype.build() 486 | 487 | #### PgInsertBuilder.prototype.conflict(target, action) 488 | 489 | #### PgInsertBuilder.prototype.returning(key, alias) 490 | 491 | ### class PgSelectBuilder extends [SelectBuilder][sql-selectbuilder] 492 | 493 | #### PgSelectBuilder.prototype.constructor(params, options) 494 | 495 | #### PgSelectBuilder.prototype.distinctOn(keyOrExpr) 496 | 497 | ### class ConditionsBuilder extends [QueryBuilder][sql-querybuilder] 498 | 499 | #### ConditionsBuilder.prototype.constructor(params, options) 500 | 501 | #### ConditionsBuilder.prototype.and(key, cond, value) 502 | 503 | #### ConditionsBuilder.prototype.andConds(conditions) 504 | 505 | #### ConditionsBuilder.prototype.andKey(leftKey, cond, rightKey) 506 | 507 | #### ConditionsBuilder.prototype.andRaw(sql) 508 | 509 | #### ConditionsBuilder.prototype.any(key, value) 510 | 511 | #### ConditionsBuilder.prototype.between(key, from, to, symmetric = false) 512 | 513 | #### ConditionsBuilder.prototype.build() 514 | 515 | #### ConditionsBuilder.prototype.exists(subquery) 516 | 517 | #### ConditionsBuilder.prototype.ilike(key, value) 518 | 519 | #### ConditionsBuilder.prototype.in(key, conds) 520 | 521 | #### ConditionsBuilder.prototype.like(key, value) 522 | 523 | #### ConditionsBuilder.prototype.not(key, cond, value) 524 | 525 | #### ConditionsBuilder.prototype.notBetween(key, from, to, symmetric = false) 526 | 527 | #### ConditionsBuilder.prototype.notILike(key, value) 528 | 529 | #### ConditionsBuilder.prototype.notIn(key, conds) 530 | 531 | #### ConditionsBuilder.prototype.notKey(leftKey, cond, rightKey) 532 | 533 | #### ConditionsBuilder.prototype.notLike(key, value) 534 | 535 | #### ConditionsBuilder.prototype.notNull(key) 536 | 537 | #### ConditionsBuilder.prototype.notRaw(sql) 538 | 539 | #### ConditionsBuilder.prototype.null(key) 540 | 541 | #### ConditionsBuilder.prototype.or(key, cond, value) 542 | 543 | #### ConditionsBuilder.prototype.orAny(key, value) 544 | 545 | #### ConditionsBuilder.prototype.orBetween(key, from, to, symmetric = false) 546 | 547 | #### ConditionsBuilder.prototype.orConds(conditions) 548 | 549 | #### ConditionsBuilder.prototype.orExists(subquery) 550 | 551 | #### ConditionsBuilder.prototype.orILike(key, value) 552 | 553 | #### ConditionsBuilder.prototype.orIn(key, conds) 554 | 555 | #### ConditionsBuilder.prototype.orKey(leftKey, cond, rightKey) 556 | 557 | #### ConditionsBuilder.prototype.orLike(key, value) 558 | 559 | #### ConditionsBuilder.prototype.orNot(key, cond, value) 560 | 561 | #### ConditionsBuilder.prototype.orNotBetween(key, from, to, symmetric = false) 562 | 563 | #### ConditionsBuilder.prototype.orNotILike(key, value) 564 | 565 | #### ConditionsBuilder.prototype.orNotIn(key, conds) 566 | 567 | #### ConditionsBuilder.prototype.orNotKey(leftKey, cond, rightKey) 568 | 569 | #### ConditionsBuilder.prototype.orNotLike(key, value) 570 | 571 | #### ConditionsBuilder.prototype.orNotNull(key) 572 | 573 | #### ConditionsBuilder.prototype.orNotRaw(sql) 574 | 575 | #### ConditionsBuilder.prototype.orNull(key) 576 | 577 | #### ConditionsBuilder.prototype.orRaw(sql) 578 | 579 | ### class ParamsBuilder 580 | 581 | Base class for all ParamsBuilders 582 | 583 | #### ParamsBuilder.prototype.constructor() 584 | 585 | Base class for all ParamsBuilders 586 | 587 | #### ParamsBuilder.prototype.add(value, options) 588 | 589 | - `value`: `` 590 | - `options`: [``][object] optional 591 | 592 | _Returns:_ [``][string] name to put in an sql query 593 | 594 | Add passed value to parameters 595 | 596 | #### ParamsBuilder.prototype.build() 597 | 598 | _Returns:_ [``][string] 599 | 600 | Build and return the SQL query 601 | 602 | ### class PostgresParamsBuilder extends [ParamsBuilder][sql-paramsbuilder] 603 | 604 | #### PostgresParamsBuilder.prototype.constructor() 605 | 606 | #### PostgresParamsBuilder.prototype.add(value, options) 607 | 608 | - `value`: `` 609 | - `options`: [``][object] optional 610 | 611 | _Returns:_ [``][string] name to put in a sql query 612 | 613 | Add passed value to parameters 614 | 615 | #### PostgresParamsBuilder.prototype.build() 616 | 617 | _Returns:_ `` 618 | 619 | Generic building method that must return the parameters object 620 | 621 | ### pg(options) 622 | 623 | ### pgSelect(options) 624 | 625 | ### pgInsert(options) 626 | 627 | ### pgUpdate(options) 628 | 629 | ### pgDelete(options) 630 | 631 | ### pgQuerySelect(pg, handler) 632 | 633 | ### pgQueryInsert(pg, handler) 634 | 635 | ### pgQueryUpdate(pg, handler) 636 | 637 | ### pgQueryDelete(pg, handler) 638 | 639 | [object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object 640 | [function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function 641 | [string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type 642 | [sql-querybuilder]: #class-querybuilder 643 | [sql-queryconditionsbuilder]: #class-queryconditionsbuilder-extends-querybuilder 644 | [sql-selectbuilder]: #class-selectbuilder-extends-queryconditionsbuilder 645 | [sql-insertbuilder]: #class-insertbuilder-extends-querybuilder 646 | [sql-paramsbuilder]: #class-paramsbuilder 647 | -------------------------------------------------------------------------------- /lib/conditions-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | makeParamValue, 5 | QueryBuilder, 6 | RawBuilder, 7 | } = require('./query-builder.js'); 8 | const { mapJoinIterable } = require('./utils.js'); 9 | 10 | const allowedConditions = new Set([ 11 | '=', 12 | '<>', 13 | '<', 14 | '<=', 15 | '>', 16 | '>=', 17 | 'LIKE', 18 | 'ILIKE', 19 | 'NOT LIKE', 20 | 'NOT ILIKE', 21 | 'EXISTS', 22 | 'IS', 23 | 'IS DISTINCT', 24 | 'IN', 25 | 'NOT IN', 26 | 'BETWEEN', 27 | 'BETWEEN SYMMETRIC', 28 | 'NOT BETWEEN', 29 | 'NOT BETWEEN SYMMETRIC', 30 | ]); 31 | 32 | const parseCondition = (cond) => { 33 | cond = cond.toUpperCase(); 34 | if (cond === '!=') return '<>'; 35 | if (!allowedConditions.has(cond)) { 36 | throw new Error(`The operator "${cond}" is not permitted`); 37 | } 38 | return cond; 39 | }; 40 | 41 | const makeWhereValue = (cond, value, params) => { 42 | if (value instanceof QueryBuilder) { 43 | return '(' + value.build() + ')'; 44 | } else if (cond === 'IN' || cond === 'NOT IN') { 45 | const conditions = mapJoinIterable(value, (v) => params.add(v), ', '); 46 | return '(' + conditions + ')'; 47 | } else if (cond && cond.endsWith('ANY')) { 48 | return '(' + params.add(value) + ')'; 49 | } else if (cond && cond.includes('BETWEEN')) { 50 | const startParam = makeParamValue(value[0], params); 51 | const endParam = makeParamValue(value[1], params); 52 | return `${startParam} AND ${endParam}`; 53 | } else { 54 | return params.add(value); 55 | } 56 | }; 57 | 58 | const handleConditionsOrFn = (conditions, builder) => { 59 | if (typeof conditions === 'function') { 60 | // eslint-disable-next-line no-use-before-define 61 | return conditions(new ConditionsBuilder(builder.params, builder.options)); 62 | } else if (conditions instanceof QueryBuilder) { 63 | return conditions; 64 | } else { 65 | throw new TypeError( 66 | `The 'conditions' must be a QueryBuilder or a function returning one` 67 | ); 68 | } 69 | }; 70 | 71 | const kCond = Symbol('kCond'); 72 | const kCondKey = Symbol('kCondKey'); 73 | const kCondRaw = Symbol('kCondRaw'); 74 | 75 | class ConditionsBuilder extends QueryBuilder { 76 | constructor(params, options) { 77 | super(params, options); 78 | this.clauses = []; 79 | } 80 | 81 | [kCond](key, cond, value, mod = '', or = false) { 82 | if (key) key = this.escapeKey(key); 83 | this.clauses.push({ key, value, cond, mod, or }); 84 | return this; 85 | } 86 | 87 | [kCondKey](leftKey, cond, rightKey, mod = '', or = false) { 88 | this.clauses.push({ 89 | key: this.escapeKey(leftKey), 90 | rightKey: this.escapeKey(rightKey), 91 | cond, 92 | mod, 93 | or, 94 | }); 95 | return this; 96 | } 97 | 98 | [kCondRaw](sql, mod = '', or = false) { 99 | this.clauses.push({ 100 | sql: new RawBuilder(sql, this.params, this.options), 101 | mod, 102 | or, 103 | }); 104 | return this; 105 | } 106 | 107 | and(key, cond, value) { 108 | if ( 109 | typeof key !== 'string' && 110 | typeof key !== 'function' && 111 | !(key instanceof QueryBuilder) 112 | ) { 113 | throw new TypeError(`The 'key' must be a string key or a QueryBuilder ' + 114 | 'or a function returning QueryBuilder`); 115 | } 116 | return typeof key !== 'string' 117 | ? this.andConds(key) 118 | : this[kCond](key, parseCondition(cond), value); 119 | } 120 | 121 | andKey(leftKey, cond, rightKey) { 122 | return this[kCondKey](leftKey, cond, rightKey); 123 | } 124 | 125 | andRaw(sql) { 126 | return this[kCondRaw](sql); 127 | } 128 | 129 | andConds(conditions) { 130 | return this[kCond]('', '', handleConditionsOrFn(conditions, this)); 131 | } 132 | 133 | or(key, cond, value) { 134 | if ( 135 | typeof key !== 'string' && 136 | typeof key !== 'function' && 137 | !(key instanceof QueryBuilder) 138 | ) { 139 | throw new TypeError(`The 'key' must be a string key or a QueryBuilder ' + 140 | 'or a function returning QueryBuilder`); 141 | } 142 | return typeof key !== 'string' 143 | ? this.orConds(key) 144 | : this[kCond](key, parseCondition(cond), value, undefined, true); 145 | } 146 | 147 | orKey(leftKey, cond, rightKey) { 148 | return this[kCondKey](leftKey, cond, rightKey, '', true); 149 | } 150 | 151 | orRaw(sql) { 152 | return this[kCondRaw](sql, '', true); 153 | } 154 | 155 | orConds(conditions) { 156 | const conds = handleConditionsOrFn(conditions, this); 157 | return this[kCond]('', '', conds, '', true); 158 | } 159 | 160 | not(key, cond, value) { 161 | return this[kCond](key, cond, value, 'NOT'); 162 | } 163 | 164 | notKey(leftKey, cond, rightKey) { 165 | return this[kCondKey](leftKey, cond, rightKey, 'NOT'); 166 | } 167 | 168 | notRaw(sql) { 169 | return this[kCondRaw](sql, 'NOT', false); 170 | } 171 | 172 | orNot(key, cond, value) { 173 | return this[kCond](key, cond, value, 'NOT', true); 174 | } 175 | 176 | orNotKey(leftKey, cond, rightKey) { 177 | return this[kCondKey](leftKey, cond, rightKey, 'NOT', true); 178 | } 179 | 180 | orNotRaw(sql) { 181 | return this[kCondRaw](sql, 'NOT', true); 182 | } 183 | 184 | null(key) { 185 | return this[kCond](key, 'IS NULL'); 186 | } 187 | 188 | orNull(key) { 189 | return this[kCond](key, 'IS NULL', undefined, '', true); 190 | } 191 | 192 | notNull(key) { 193 | return this[kCond](key, 'IS NOT NULL'); 194 | } 195 | 196 | orNotNull(key) { 197 | return this[kCond](key, 'IS NOT NULL', undefined, '', true); 198 | } 199 | 200 | between(key, from, to, symmetric = false) { 201 | const cond = symmetric ? 'BETWEEN SYMMETRIC' : 'BETWEEN'; 202 | return this[kCond](key, cond, [from, to]); 203 | } 204 | 205 | orBetween(key, from, to, symmetric = false) { 206 | const cond = symmetric ? 'BETWEEN SYMMETRIC' : 'BETWEEN'; 207 | return this[kCond](key, cond, [from, to], '', true); 208 | } 209 | 210 | notBetween(key, from, to, symmetric = false) { 211 | const cond = symmetric ? 'NOT BETWEEN SYMMETRIC' : 'NOT BETWEEN'; 212 | return this[kCond](key, cond, [from, to]); 213 | } 214 | 215 | orNotBetween(key, from, to, symmetric = false) { 216 | const cond = symmetric ? 'NOT BETWEEN SYMMETRIC' : 'NOT BETWEEN'; 217 | return this[kCond](key, cond, [from, to], '', true); 218 | } 219 | 220 | in(key, conds) { 221 | return this[kCond](key, 'IN', conds); 222 | } 223 | 224 | orIn(key, conds) { 225 | return this[kCond](key, 'IN', conds, '', true); 226 | } 227 | 228 | notIn(key, conds) { 229 | return this[kCond](key, 'NOT IN', conds); 230 | } 231 | 232 | orNotIn(key, conds) { 233 | return this[kCond](key, 'NOT IN', conds, '', true); 234 | } 235 | 236 | any(key, value) { 237 | return this[kCond](key, '= ANY', value); 238 | } 239 | 240 | orAny(key, value) { 241 | return this[kCond](key, '= ANY', value, '', true); 242 | } 243 | 244 | exists(subquery) { 245 | return this[kCond]('', 'EXISTS', subquery); 246 | } 247 | 248 | orExists(subquery) { 249 | return this[kCond]('', 'EXISTS', subquery, '', true); 250 | } 251 | 252 | like(key, value) { 253 | return this[kCond](key, 'LIKE', value); 254 | } 255 | 256 | notLike(key, value) { 257 | return this[kCond](key, 'NOT LIKE', value); 258 | } 259 | 260 | orLike(key, value) { 261 | return this[kCond](key, 'LIKE', value, '', true); 262 | } 263 | 264 | orNotLike(key, value) { 265 | return this[kCond](key, 'NOT LIKE', value, '', true); 266 | } 267 | 268 | ilike(key, value) { 269 | return this[kCond](key, 'ILIKE', value); 270 | } 271 | 272 | notILike(key, value) { 273 | return this[kCond](key, 'NOT ILIKE', value); 274 | } 275 | 276 | orILike(key, value) { 277 | return this[kCond](key, 'ILIKE', value, '', true); 278 | } 279 | 280 | orNotILike(key, value) { 281 | return this[kCond](key, 'NOT ILIKE', value, '', true); 282 | } 283 | 284 | build() { 285 | const { clauses } = this; 286 | let res = ''; 287 | for (let i = 0; i < clauses.length; ++i) { 288 | const clause = clauses[i]; 289 | if (i !== 0) res += clause.or ? ' OR ' : ' AND '; 290 | if (clause.mod) res += `${clause.mod} `; 291 | if (clause.sql) { 292 | res += clause.sql.build(); 293 | continue; 294 | } 295 | if (clause.key) res += `${clause.key} `; 296 | if (clause.cond) res += clause.cond; 297 | if (clause.value !== undefined) { 298 | if (clause.cond) res += ' '; 299 | res += makeWhereValue(clause.cond, clause.value, this.params); 300 | } 301 | if (clause.rightKey !== undefined) { 302 | if (clause.cond) res += ' '; 303 | res += clause.rightKey; 304 | } 305 | } 306 | return res; 307 | } 308 | } 309 | 310 | module.exports = { 311 | ConditionsBuilder, 312 | }; 313 | -------------------------------------------------------------------------------- /lib/delete-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { QueryConditionsBuilder } = require('./query-conditions-builder.js'); 4 | const { selectSubqueryBuilder } = require('./select-builder.js'); 5 | const { makeKeyWithAlias } = require('./query-builder.js'); 6 | 7 | class DeleteBuilder extends QueryConditionsBuilder { 8 | constructor(params, options) { 9 | super(params, options); 10 | this.operations = { from: null }; 11 | } 12 | 13 | from(tableName, alias) { 14 | this.operations.from = { 15 | table: this.escapeIdentifier(tableName), 16 | alias: alias && this.escapeIdentifier(alias), 17 | }; 18 | return this; 19 | } 20 | 21 | // #private 22 | _whereValueMapper(subquery) { 23 | return selectSubqueryBuilder(subquery, this); 24 | } 25 | 26 | build() { 27 | let query = 'DELETE FROM '; 28 | 29 | if (!this.operations.from) { 30 | throw new Error('Cannot generate SQL, tableName is not defined'); 31 | } 32 | query += makeKeyWithAlias(this.operations.from); 33 | 34 | const whereClauses = this.whereConditions.build(); 35 | if (whereClauses.length > 0) { 36 | query += ` WHERE ${whereClauses}`; 37 | } 38 | 39 | return query; 40 | } 41 | } 42 | 43 | module.exports = { DeleteBuilder }; 44 | -------------------------------------------------------------------------------- /lib/insert-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | SelectBuilder, 5 | processWith, 6 | selectSubqueryBuilder, 7 | } = require('./select-builder.js'); 8 | const { mapJoinIterable } = require('./utils.js'); 9 | const { makeKeyWithAlias, QueryBuilder } = require('./query-builder.js'); 10 | 11 | class InsertBuilder extends QueryBuilder { 12 | constructor(params, options) { 13 | super(params, options); 14 | this.operations = { 15 | table: null, 16 | values: [], 17 | with: [], 18 | from: null, 19 | }; 20 | } 21 | 22 | table(tableName, alias) { 23 | this.operations.table = { 24 | table: this.escapeIdentifier(tableName), 25 | alias: alias && this.escapeIdentifier(alias), 26 | }; 27 | return this; 28 | } 29 | 30 | with(alias, sql) { 31 | this.operations.with.push({ sql, alias: this.makeKeyOrExpr(alias) }); 32 | return this; 33 | } 34 | 35 | from(sql) { 36 | if (this.operations.values.length > 0) { 37 | throw new Error('Cannot use query insert with values'); 38 | } 39 | this.operations.from = sql; 40 | } 41 | 42 | select() { 43 | return new SelectBuilder(this.params, this.options); 44 | } 45 | 46 | value(key, value) { 47 | if (this.operations.from) { 48 | throw new Error('Cannot use value with query insert'); 49 | } 50 | this.operations.values.push({ 51 | key: this.escapeIdentifier(key), 52 | value: this._valueMapper(value), 53 | }); 54 | return this; 55 | } 56 | 57 | values(obj) { 58 | for (const [key, value] of Object.entries(obj)) { 59 | if (value !== undefined) { 60 | this.value(key, value); 61 | } 62 | } 63 | return this; 64 | } 65 | 66 | // #private 67 | _valueMapper(subquery) { 68 | return selectSubqueryBuilder(subquery, this); 69 | } 70 | 71 | // #private 72 | _processValues(items) { 73 | const keys = mapJoinIterable(items, (v) => v.key, ', '); 74 | const values = mapJoinIterable( 75 | items, 76 | (v) => this.makeParamValue(v.value), 77 | ', ' 78 | ); 79 | return `(${keys}) VALUES (${values})`; 80 | } 81 | 82 | build() { 83 | let query = ''; 84 | 85 | if (this.operations.with.length > 0) { 86 | query += 87 | processWith(this.operations.with, this._valueMapper.bind(this)) + ' '; 88 | } 89 | 90 | query += 'INSERT INTO '; 91 | 92 | if (!this.operations.table) { 93 | throw new Error('Cannot generate SQL, tableName is not defined'); 94 | } 95 | query += makeKeyWithAlias(this.operations.table); 96 | 97 | if (this.operations.from) { 98 | const builder = selectSubqueryBuilder(this.operations.from, this); 99 | const sql = builder instanceof QueryBuilder ? builder.build() : builder; 100 | query += ` ${sql}`; 101 | } else { 102 | query += ` ${this._processValues(this.operations.values)}`; 103 | } 104 | 105 | return query; 106 | } 107 | } 108 | 109 | module.exports = { InsertBuilder }; 110 | -------------------------------------------------------------------------------- /lib/params-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Base class for all ParamsBuilders 4 | class ParamsBuilder { 5 | // Add passed value to parameters 6 | // value 7 | // options optional 8 | // Returns: name to put in an sql query 9 | add() { 10 | throw new Error('Not implemented'); 11 | } 12 | 13 | // Generic building method that must return the parameters object 14 | // Returns: 15 | build() { 16 | throw new Error('Not implemented'); 17 | } 18 | } 19 | 20 | module.exports = { ParamsBuilder }; 21 | -------------------------------------------------------------------------------- /lib/pg-insert-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { pgEscapeIdentifier, mapJoinIterable } = require('./utils.js'); 4 | const { InsertBuilder } = require('./insert-builder.js'); 5 | 6 | class PgInsertBuilder extends InsertBuilder { 7 | constructor(params, options) { 8 | super(params, { escapeIdentifier: pgEscapeIdentifier, ...options }); 9 | this.operations = { 10 | ...this.operations, 11 | returning: [], 12 | conflict: null, 13 | }; 14 | } 15 | 16 | returning(key, alias) { 17 | this.operations.returning.push({ 18 | key, 19 | alias: alias && this.escapeIdentifier(alias), 20 | }); 21 | return this; 22 | } 23 | 24 | conflict(target, action) { 25 | this.operations.conflict = { target, action }; 26 | return this; 27 | } 28 | 29 | // #private 30 | _processReturning(returning) { 31 | if (returning[0]?.key === '*') return '*'; 32 | return mapJoinIterable( 33 | returning, 34 | (r) => this.makeKeyOrExpr(r.key) + (r.alias ? ` AS ${r.alias}` : ''), 35 | ', ' 36 | ); 37 | } 38 | 39 | build() { 40 | let query = super.build(); 41 | 42 | const { conflict } = this.operations; 43 | if (conflict) { 44 | query += ` ON CONFLICT ${conflict.target ?? ''} ${conflict.action}`; 45 | } 46 | 47 | const { returning } = this.operations; 48 | if (returning?.length > 0) { 49 | query += ` RETURNING ${this._processReturning(returning)}`; 50 | } 51 | 52 | return query; 53 | } 54 | } 55 | 56 | module.exports = { PgInsertBuilder }; 57 | -------------------------------------------------------------------------------- /lib/pg-params-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ParamsBuilder } = require('./params-builder.js'); 4 | 5 | class PostgresParamsBuilder extends ParamsBuilder { 6 | constructor() { 7 | super(); 8 | this.params = []; 9 | } 10 | 11 | // Add passed value to parameters 12 | // value 13 | // options optional 14 | // Returns: name to put in a sql query 15 | add(value) { 16 | this.params.push(value); 17 | return `$${this.params.length}`; 18 | } 19 | 20 | // Generic building method that must return the parameters object 21 | // Returns: 22 | build() { 23 | return this.params; 24 | } 25 | } 26 | 27 | module.exports = { PostgresParamsBuilder }; 28 | -------------------------------------------------------------------------------- /lib/pg-select-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { SelectBuilder } = require('./select-builder.js'); 4 | const { mapJoinIterable, pgEscapeIdentifier } = require('./utils.js'); 5 | 6 | class PgSelectBuilder extends SelectBuilder { 7 | constructor(params, options) { 8 | super(params, { escapeIdentifier: pgEscapeIdentifier, ...options }); 9 | this.operations = { 10 | ...this.operations, 11 | selectDistinctOn: [], 12 | }; 13 | } 14 | 15 | distinctOn(keyOrExpr) { 16 | this.operations.selectDistinct = true; 17 | this.operations.selectDistinctOn.push(keyOrExpr); 18 | return this; 19 | } 20 | 21 | // #private 22 | _whereValueMapper(subquery) { 23 | return pgSelectSubqueryBuilder(subquery, this); 24 | } 25 | 26 | // #private 27 | _processDistinct() { 28 | const distinctOn = this.operations.selectDistinctOn; 29 | if (distinctOn.length === 0) { 30 | return super._processDistinct(); 31 | } 32 | const keys = mapJoinIterable( 33 | distinctOn, 34 | (v) => this.makeKeyOrExpr(v), 35 | ', ' 36 | ); 37 | return `DISTINCT ON (${keys})`; 38 | } 39 | } 40 | 41 | function pgSelectSubqueryBuilder(subquery, builder) { 42 | return typeof subquery === 'function' 43 | ? subquery(new PgSelectBuilder(builder.params, builder.options)) 44 | : subquery; 45 | } 46 | 47 | module.exports = { PgSelectBuilder, pgSelectSubqueryBuilder }; 48 | -------------------------------------------------------------------------------- /lib/pg.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { PostgresParamsBuilder } = require('./pg-params-builder.js'); 4 | const { UpdateBuilder } = require('./update-builder.js'); 5 | const { DeleteBuilder } = require('./delete-builder.js'); 6 | const { PgInsertBuilder } = require('./pg-insert-builder.js'); 7 | const { PgSelectBuilder } = require('./pg-select-builder.js'); 8 | const { pgEscapeIdentifier } = require('./utils.js'); 9 | 10 | const pgSelect = (options) => { 11 | const params = options?.params ?? new PostgresParamsBuilder(); 12 | return new PgSelectBuilder(params, { 13 | escapeIdentifier: pgEscapeIdentifier, 14 | ...options, 15 | }); 16 | }; 17 | 18 | const pg = pgSelect; 19 | 20 | const pgInsert = (options) => { 21 | const params = options?.params ?? new PostgresParamsBuilder(); 22 | return new PgInsertBuilder(params, { 23 | escapeIdentifier: pgEscapeIdentifier, 24 | ...options, 25 | }); 26 | }; 27 | 28 | const pgUpdate = (options) => { 29 | const params = new PostgresParamsBuilder(); 30 | return new UpdateBuilder(params, { 31 | escapeIdentifier: pgEscapeIdentifier, 32 | ...options, 33 | }); 34 | }; 35 | 36 | const pgDelete = (options) => { 37 | const params = new PostgresParamsBuilder(); 38 | return new DeleteBuilder(params, { 39 | escapeIdentifier: pgEscapeIdentifier, 40 | ...options, 41 | }); 42 | }; 43 | 44 | const pgQuerySelect = (pg, handler) => { 45 | const builder = pgSelect(); 46 | handler(builder, builder.params); 47 | return pg.query(builder.build(), builder.buildParams()); 48 | }; 49 | 50 | const pgQueryInsert = (pg, handler) => { 51 | const builder = pgInsert(); 52 | handler(builder, builder.params); 53 | return pg.query(builder.build(), builder.buildParams()); 54 | }; 55 | 56 | const pgQueryUpdate = (pg, handler) => { 57 | const builder = pgUpdate(); 58 | handler(builder, builder.params); 59 | return pg.query(builder.build(), builder.buildParams()); 60 | }; 61 | 62 | const pgQueryDelete = (pg, handler) => { 63 | const builder = pgDelete(); 64 | handler(builder, builder.params); 65 | return pg.query(builder.build(), builder.buildParams()); 66 | }; 67 | 68 | module.exports = { 69 | pg, 70 | pgSelect, 71 | pgInsert, 72 | pgUpdate, 73 | pgDelete, 74 | pgQuerySelect, 75 | pgQueryInsert, 76 | pgQueryUpdate, 77 | pgQueryDelete, 78 | }; 79 | -------------------------------------------------------------------------------- /lib/query-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | escapeIdentifier: defaultEscapeIdentifier, 5 | escapeKey, 6 | } = require('./utils.js'); 7 | 8 | class QueryBuilder { 9 | // params 10 | // options 11 | // escapeIdentifier 12 | // identifier to escape 13 | // Returns: escaped string 14 | constructor(params, options = {}) { 15 | this.params = params; 16 | this.options = options; 17 | this.escapeIdentifier = options.escapeIdentifier || defaultEscapeIdentifier; 18 | this.escapeKey = (key) => escapeKey(key, this.escapeIdentifier); 19 | } 20 | 21 | makeParamValue(value) { 22 | return makeParamValue(value, this.params); 23 | } 24 | 25 | makeKeyOrExpr(value, wrap = false) { 26 | if (value instanceof QueryBuilder) { 27 | return wrap ? `(${value.build()})` : value.build(); 28 | } 29 | return this.escapeKey(value); 30 | } 31 | 32 | raw(sqlTemplate) { 33 | // eslint-disable-next-line no-use-before-define 34 | return new RawBuilder(sqlTemplate, this.params, this.options); 35 | } 36 | 37 | key(key) { 38 | return this.raw(this.escapeKey(key)); 39 | } 40 | 41 | nested() { 42 | return new this.constructor(this.params, this.options); 43 | } 44 | 45 | // Build and return the SQL query 46 | // Returns: 47 | build() { 48 | throw new Error('Not implemented'); 49 | } 50 | 51 | // Build params for this query 52 | // Returns: > 53 | buildParams() { 54 | return this.params.build(); 55 | } 56 | } 57 | 58 | class RawBuilder extends QueryBuilder { 59 | // sqlTemplate function or sql string 60 | // params 61 | // Returns: query 62 | constructor(sqlTemplate, params, options) { 63 | super(params, options); 64 | this.sqlTemplate = sqlTemplate; 65 | } 66 | 67 | build() { 68 | return typeof this.sqlTemplate === 'function' 69 | ? this.sqlTemplate(this.params) 70 | : this.sqlTemplate; 71 | } 72 | } 73 | 74 | function makeParamValue(value, params) { 75 | if (value instanceof QueryBuilder) { 76 | return '(' + value.build() + ')'; 77 | } 78 | // Stringify plain objects for db. 79 | if (Object.prototype.toString.call(value) === '[object Object]') { 80 | return params.add(JSON.stringify(value)); 81 | } 82 | return params.add(value); 83 | } 84 | 85 | function makeKeyWithAlias(table) { 86 | return !table.alias 87 | ? String(table.table) 88 | : `${table.table} AS ${table.alias}`; 89 | } 90 | 91 | function checkTypeOrQuery(value, name, type) { 92 | if (!(value instanceof QueryBuilder) && typeof value !== type) { 93 | throw new TypeError( 94 | `Invalid '${name}' value type, expected type ${type} or QueryBuilder. ` + 95 | `Received: ${value}` 96 | ); 97 | } 98 | } 99 | 100 | module.exports = { 101 | QueryBuilder, 102 | RawBuilder, 103 | makeParamValue, 104 | makeKeyWithAlias, 105 | checkTypeOrQuery, 106 | }; 107 | -------------------------------------------------------------------------------- /lib/query-conditions-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { QueryBuilder } = require('./query-builder.js'); 4 | const { ConditionsBuilder } = require('./conditions-builder.js'); 5 | 6 | class QueryConditionsBuilder extends QueryBuilder { 7 | constructor(params, options) { 8 | super(params, options); 9 | this.whereConditions = new ConditionsBuilder(params, options); 10 | } 11 | 12 | where(key, cond, value) { 13 | this.whereConditions.and(key, cond, this._whereValueMapper(value)); 14 | return this; 15 | } 16 | 17 | whereKey(leftKey, cond, rightKey) { 18 | this.whereConditions.andKey(leftKey, cond, rightKey); 19 | return this; 20 | } 21 | 22 | whereRaw(sql) { 23 | this.whereConditions.andRaw(sql); 24 | return this; 25 | } 26 | 27 | orWhere(key, cond, value) { 28 | this.whereConditions.or(key, cond, this._whereValueMapper(value)); 29 | return this; 30 | } 31 | 32 | orWhereKey(leftKey, cond, rightKey) { 33 | this.whereConditions.orKey(leftKey, cond, rightKey); 34 | return this; 35 | } 36 | 37 | orWhereRaw(sql) { 38 | this.whereConditions.orRaw(sql); 39 | return this; 40 | } 41 | 42 | whereEq(key, value) { 43 | this.whereConditions.and(key, '=', this._whereValueMapper(value)); 44 | return this; 45 | } 46 | 47 | orWhereEq(key, value) { 48 | this.whereConditions.or(key, '=', this._whereValueMapper(value)); 49 | return this; 50 | } 51 | 52 | whereMore(key, value) { 53 | this.whereConditions.and(key, '>', this._whereValueMapper(value)); 54 | return this; 55 | } 56 | 57 | orWhereMore(key, value) { 58 | this.whereConditions.or(key, '>', this._whereValueMapper(value)); 59 | return this; 60 | } 61 | 62 | whereMoreEq(key, value) { 63 | this.whereConditions.and(key, '>=', this._whereValueMapper(value)); 64 | return this; 65 | } 66 | 67 | orWhereMoreEq(key, value) { 68 | this.whereConditions.or(key, '>=', this._whereValueMapper(value)); 69 | return this; 70 | } 71 | 72 | whereLess(key, value) { 73 | this.whereConditions.and(key, '<', this._whereValueMapper(value)); 74 | return this; 75 | } 76 | 77 | orWhereLess(key, value) { 78 | this.whereConditions.or(key, '<', this._whereValueMapper(value)); 79 | return this; 80 | } 81 | 82 | whereLessEq(key, value) { 83 | this.whereConditions.and(key, '<=', this._whereValueMapper(value)); 84 | return this; 85 | } 86 | 87 | orWhereLessEq(key, value) { 88 | this.whereConditions.or(key, '<=', this._whereValueMapper(value)); 89 | return this; 90 | } 91 | 92 | whereNot(key, cond, value) { 93 | this.whereConditions.not(key, cond, this._whereValueMapper(value)); 94 | return this; 95 | } 96 | 97 | whereNotKey(leftKey, cond, rightKey) { 98 | this.whereConditions.notKey(leftKey, cond, rightKey); 99 | return this; 100 | } 101 | 102 | whereNotRaw(sql) { 103 | this.whereConditions.notRaw(sql); 104 | return this; 105 | } 106 | 107 | orWhereNot(key, cond, value) { 108 | this.whereConditions.orNot(key, cond, this._whereValueMapper(value)); 109 | return this; 110 | } 111 | 112 | orWhereNotKey(leftKey, cond, rightKey) { 113 | this.whereConditions.orNotKey(leftKey, cond, rightKey); 114 | return this; 115 | } 116 | 117 | orWhereNotRaw(sql) { 118 | this.whereConditions.orNotRaw(sql); 119 | return this; 120 | } 121 | 122 | whereNull(key) { 123 | this.whereConditions.null(key); 124 | return this; 125 | } 126 | 127 | orWhereNull(key) { 128 | this.whereConditions.orNull(key); 129 | return this; 130 | } 131 | 132 | whereNotNull(key) { 133 | this.whereConditions.notNull(key); 134 | return this; 135 | } 136 | 137 | orWhereNotNull(key) { 138 | this.whereConditions.orNotNull(key); 139 | return this; 140 | } 141 | 142 | whereBetween(key, from, to, symmetric) { 143 | this.whereConditions.between( 144 | key, 145 | this._whereValueMapper(from), 146 | this._whereValueMapper(to), 147 | symmetric 148 | ); 149 | return this; 150 | } 151 | 152 | orWhereBetween(key, from, to, symmetric) { 153 | this.whereConditions.orBetween( 154 | key, 155 | this._whereValueMapper(from), 156 | this._whereValueMapper(to), 157 | symmetric 158 | ); 159 | return this; 160 | } 161 | 162 | whereNotBetween(key, from, to, symmetric) { 163 | this.whereConditions.notBetween( 164 | key, 165 | this._whereValueMapper(from), 166 | this._whereValueMapper(to), 167 | symmetric 168 | ); 169 | return this; 170 | } 171 | 172 | orWhereNotBetween(key, from, to, symmetric) { 173 | this.whereConditions.orNotBetween( 174 | key, 175 | this._whereValueMapper(from), 176 | this._whereValueMapper(to), 177 | symmetric 178 | ); 179 | return this; 180 | } 181 | 182 | whereIn(key, conds) { 183 | this.whereConditions.in(key, this._whereValueMapper(conds)); 184 | return this; 185 | } 186 | 187 | orWhereIn(key, conds) { 188 | this.whereConditions.orIn(key, this._whereValueMapper(conds)); 189 | return this; 190 | } 191 | 192 | whereNotIn(key, conds) { 193 | this.whereConditions.notIn(key, this._whereValueMapper(conds)); 194 | return this; 195 | } 196 | 197 | orWhereNotIn(key, conds) { 198 | this.whereConditions.orNotIn(key, this._whereValueMapper(conds)); 199 | return this; 200 | } 201 | 202 | whereAny(key, value) { 203 | this.whereConditions.any(key, this._whereValueMapper(value)); 204 | return this; 205 | } 206 | 207 | orWhereAny(key, value) { 208 | this.whereConditions.orAny(key, this._whereValueMapper(value)); 209 | return this; 210 | } 211 | 212 | whereExists(subquery) { 213 | this.whereConditions.exists(this._whereValueMapper(subquery)); 214 | return this; 215 | } 216 | 217 | orWhereExists(subquery) { 218 | this.whereConditions.orExists(this._whereValueMapper(subquery)); 219 | return this; 220 | } 221 | 222 | whereLike(key, value) { 223 | this.whereConditions.like(key, value); 224 | return this; 225 | } 226 | 227 | whereNotLike(key, value) { 228 | this.whereConditions.notLike(key, value); 229 | return this; 230 | } 231 | 232 | orWhereLike(key, value) { 233 | this.whereConditions.orLike(key, value); 234 | return this; 235 | } 236 | 237 | orWhereNotLike(key, value) { 238 | this.whereConditions.orNotLike(key, value); 239 | return this; 240 | } 241 | 242 | whereILike(key, value) { 243 | this.whereConditions.ilike(key, value); 244 | return this; 245 | } 246 | 247 | whereNotILike(key, value) { 248 | this.whereConditions.notILike(key, value); 249 | return this; 250 | } 251 | 252 | orWhereILike(key, value) { 253 | this.whereConditions.orILike(key, value); 254 | return this; 255 | } 256 | 257 | orWhereNotILike(key, value) { 258 | this.whereConditions.orNotILike(key, value); 259 | return this; 260 | } 261 | 262 | _whereValueMapper(value) { 263 | return value; 264 | } 265 | } 266 | 267 | module.exports = { QueryConditionsBuilder }; 268 | -------------------------------------------------------------------------------- /lib/select-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | QueryBuilder, 5 | checkTypeOrQuery, 6 | makeKeyWithAlias, 7 | } = require('./query-builder.js'); 8 | const { QueryConditionsBuilder } = require('./query-conditions-builder.js'); 9 | const { mapJoinIterable } = require('./utils.js'); 10 | 11 | const functionHandlers = { 12 | default: (op) => `${op.type}(${op.field})`, 13 | }; 14 | 15 | class SelectBuilder extends QueryConditionsBuilder { 16 | constructor(params, options) { 17 | super(params, options); 18 | this.operations = { 19 | select: new Set(), 20 | selectDistinct: false, 21 | join: [], 22 | groupBy: new Set(), 23 | orderBy: [], 24 | from: [], 25 | fromRaw: null, 26 | with: [], 27 | limit: null, 28 | offset: null, 29 | }; 30 | } 31 | 32 | from(tableName, alias) { 33 | this.operations.from.push({ 34 | table: this.escapeIdentifier(tableName), 35 | alias: alias && this.escapeIdentifier(alias), 36 | }); 37 | return this; 38 | } 39 | 40 | fromRaw(sql) { 41 | this.operations.fromRaw = sql; 42 | return this; 43 | } 44 | 45 | with(alias, sql) { 46 | this.operations.with.push({ sql, alias: this.makeKeyOrExpr(alias) }); 47 | return this; 48 | } 49 | 50 | select(...fields) { 51 | for (const f of fields) { 52 | this.selectAs(f, null); 53 | } 54 | return this; 55 | } 56 | 57 | selectAs(fieldOrBuilder, alias) { 58 | return this._addSelectClause(undefined, fieldOrBuilder, alias); 59 | } 60 | 61 | selectFn(fn, field, alias) { 62 | return this._addSelectClause(fn, field, alias); 63 | } 64 | 65 | selectRaw(sqlOrBuilder) { 66 | this.operations.select.add({ raw: sqlOrBuilder }); 67 | return this; 68 | } 69 | 70 | innerJoin(tableName, leftKey, rightKey) { 71 | return this.innerJoinAs(tableName, null, leftKey, rightKey); 72 | } 73 | 74 | innerJoinAs(tableName, alias, leftKey, rightKey) { 75 | this.join('INNER', tableName, alias, leftKey, rightKey); 76 | return this; 77 | } 78 | 79 | innerJoinCond(tableName, condition) { 80 | return this.innerJoinCondAs(tableName, null, condition); 81 | } 82 | 83 | innerJoinCondAs(tableName, alias, condition) { 84 | this.joinCond('INNER', tableName, alias, condition); 85 | return this; 86 | } 87 | 88 | leftJoin(tableName, leftKey, rightKey) { 89 | return this.leftJoinAs(tableName, null, leftKey, rightKey); 90 | } 91 | 92 | leftJoinAs(tableName, alias, leftKey, rightKey) { 93 | this.join('LEFT OUTER', tableName, alias, leftKey, rightKey); 94 | return this; 95 | } 96 | 97 | leftJoinCond(tableName, condition) { 98 | return this.leftJoinCondAs(tableName, null, condition); 99 | } 100 | 101 | leftJoinCondAs(tableName, alias, condition) { 102 | this.joinCond('LEFT OUTER', tableName, alias, condition); 103 | return this; 104 | } 105 | 106 | rightJoin(tableName, leftKey, rightKey) { 107 | return this.rightJoinAs(tableName, null, leftKey, rightKey); 108 | } 109 | 110 | rightJoinAs(tableName, alias, leftKey, rightKey) { 111 | this.join('RIGHT OUTER', tableName, alias, leftKey, rightKey); 112 | return this; 113 | } 114 | 115 | rightJoinCond(tableName, condition) { 116 | return this.rightJoinCondAs(tableName, null, condition); 117 | } 118 | 119 | rightJoinCondAs(tableName, alias, condition) { 120 | this.joinCond('RIGHT OUTER', tableName, alias, condition); 121 | return this; 122 | } 123 | 124 | fullJoin(tableName, leftKey, rightKey) { 125 | return this.fullJoinAs(tableName, null, leftKey, rightKey); 126 | } 127 | 128 | fullJoinAs(tableName, alias, leftKey, rightKey) { 129 | this.join('FULL OUTER', tableName, alias, leftKey, rightKey); 130 | return this; 131 | } 132 | 133 | fullJoinCond(tableName, condition) { 134 | return this.fullJoinCondAs(tableName, null, condition); 135 | } 136 | 137 | fullJoinCondAs(tableName, alias, condition) { 138 | this.joinCond('FULL OUTER', tableName, alias, condition); 139 | return this; 140 | } 141 | 142 | naturalJoin(tableName) { 143 | return this.naturalJoinAs(tableName, null, null, null); 144 | } 145 | 146 | naturalJoinAs(tableName, alias) { 147 | this.join('NATURAL', tableName, alias, null, null); 148 | return this; 149 | } 150 | 151 | crossJoin(tableName) { 152 | return this.crossJoinAs(tableName, null, null, null); 153 | } 154 | 155 | crossJoinAs(tableName, alias) { 156 | this.join('CROSS', tableName, alias, null, null); 157 | return this; 158 | } 159 | 160 | join(kind, tableName, alias, leftKey, rightKey) { 161 | this.operations.join.push({ 162 | kind, 163 | table: this.escapeIdentifier(tableName), 164 | alias: this.escapeIdentifier(alias), 165 | leftKey: this.escapeKey(leftKey), 166 | rightKey: this.escapeKey(rightKey), 167 | }); 168 | return this; 169 | } 170 | 171 | joinCond(kind, tableName, alias, condition) { 172 | checkTypeOrQuery(condition, 'condition', 'string'); 173 | this.operations.join.push({ 174 | kind, 175 | table: this.escapeIdentifier(tableName), 176 | alias: this.escapeIdentifier(alias), 177 | condition, 178 | }); 179 | return this; 180 | } 181 | 182 | distinct() { 183 | this.operations.selectDistinct = true; 184 | return this; 185 | } 186 | 187 | orderBy(field, dir = 'ASC') { 188 | this.operations.orderBy.push({ 189 | field: this.escapeKey(field), 190 | dir: dir.toUpperCase(), 191 | }); 192 | return this; 193 | } 194 | 195 | orderByRaw(raw) { 196 | checkTypeOrQuery(raw, 'order by raw', 'string'); 197 | this.operations.orderBy.push({ raw }); 198 | return this; 199 | } 200 | 201 | groupBy(...fields) { 202 | const groupBy = this.operations.groupBy; 203 | for (const f of fields) { 204 | groupBy.add(this.escapeKey(f)); 205 | } 206 | return this; 207 | } 208 | 209 | groupByRaw(raw) { 210 | checkTypeOrQuery(raw, 'group by raw', 'string'); 211 | this.operations.groupBy.add(raw); 212 | return this; 213 | } 214 | 215 | limit(limit) { 216 | checkTypeOrQuery(limit, 'limit', 'number'); 217 | this.operations.limit = limit; 218 | return this; 219 | } 220 | 221 | offset(offset) { 222 | checkTypeOrQuery(offset, 'offset', 'number'); 223 | this.operations.offset = offset; 224 | return this; 225 | } 226 | 227 | _addSelectClause(type, field, alias) { 228 | if (alias) alias = this.escapeIdentifier(alias); 229 | 230 | if (field instanceof QueryBuilder || typeof field === 'function') { 231 | this.operations.select.add({ type, sql: field, alias }); 232 | } else { 233 | this.operations.select.add({ type, field: this.escapeKey(field), alias }); 234 | } 235 | 236 | return this; 237 | } 238 | 239 | count(field = '*', alias) { 240 | return this._addSelectClause('count', field, alias); 241 | } 242 | 243 | countOver(field = '*', alias) { 244 | this.operations.select.add({ 245 | type: 'count', 246 | sql: `COUNT(${this.escapeKey(field)}) OVER()`, 247 | alias: alias && this.escapeIdentifier(alias), 248 | }); 249 | return this; 250 | } 251 | 252 | avg(field, alias) { 253 | return this._addSelectClause('avg', field, alias); 254 | } 255 | 256 | min(field, alias) { 257 | return this._addSelectClause('min', field, alias); 258 | } 259 | 260 | max(field, alias) { 261 | return this._addSelectClause('max', field, alias); 262 | } 263 | 264 | sum(field, alias) { 265 | return this._addSelectClause('sum', field, alias); 266 | } 267 | 268 | // #private 269 | _whereValueMapper(subquery) { 270 | return selectSubqueryBuilder(subquery, this); 271 | } 272 | 273 | // #private 274 | _processFrom(from, fromRaw) { 275 | if (fromRaw) { 276 | return buildSqlOrBuilder(fromRaw, this._whereValueMapper.bind(this)); 277 | } 278 | return mapJoinIterable(from, makeKeyWithAlias, ', '); 279 | } 280 | 281 | // #private 282 | _processDistinct() { 283 | return 'DISTINCT'; 284 | } 285 | 286 | // #private 287 | _processSelect(select) { 288 | if (select.size === 0) return '*'; 289 | return mapJoinIterable( 290 | select, 291 | (op) => { 292 | if (typeof op === 'string') return op; 293 | if (op.raw) { 294 | return typeof op.raw === 'string' 295 | ? op.raw 296 | : this._whereValueMapper(op.raw).build(); 297 | } 298 | const alias = op.alias ? ` AS ${op.alias}` : ''; 299 | if (op.sql) { 300 | const sql = 301 | typeof op.sql === 'string' 302 | ? op.sql 303 | : this._whereValueMapper(op.sql).build(); 304 | return `(${sql})${alias}`; 305 | } 306 | if (op.type) { 307 | const handler = functionHandlers[op.type] ?? functionHandlers.default; 308 | return handler(op) + alias; 309 | } 310 | return op.field + alias; 311 | }, 312 | ', ' 313 | ); 314 | } 315 | 316 | // #private 317 | _processJoin(joins) { 318 | let clauses = ''; 319 | for (const join of joins) { 320 | const alias = join.alias ? ` AS ${join.alias}` : ''; 321 | if (join.kind === 'NATURAL' || join.kind === 'CROSS') { 322 | clauses += ` ${join.kind} JOIN ${join.table}${alias}`; 323 | } else if (join.condition) { 324 | const condition = 325 | join.condition instanceof QueryBuilder 326 | ? join.condition.build() 327 | : join.condition; 328 | clauses += ` ${join.kind} JOIN ${join.table}${alias} ON ${condition}`; 329 | } else { 330 | clauses += ` ${join.kind} JOIN ${join.table}${alias} ON ${join.leftKey} = ${join.rightKey}`; 331 | } 332 | } 333 | return clauses; 334 | } 335 | 336 | // #private 337 | _processOrder(clauses) { 338 | return mapJoinIterable( 339 | clauses, 340 | (o) => { 341 | if (o.raw) { 342 | return typeof o.raw === 'string' ? o.raw : o.raw.build(); 343 | } 344 | return `${o.field} ${o.dir}`; 345 | }, 346 | ', ' 347 | ); 348 | } 349 | 350 | // #private 351 | _processGroupBy(clauses) { 352 | return mapJoinIterable( 353 | clauses, 354 | (g) => (typeof g === 'string' ? g : g.build()), 355 | ', ' 356 | ); 357 | } 358 | 359 | build() { 360 | let query = ''; 361 | 362 | if (this.operations.with.length > 0) { 363 | query += 364 | processWith(this.operations.with, this._whereValueMapper.bind(this)) + 365 | ' '; 366 | } 367 | 368 | query += 'SELECT'; 369 | 370 | if (this.operations.selectDistinct) query += ` ${this._processDistinct()}`; 371 | 372 | query += ' ' + this._processSelect(this.operations.select); 373 | 374 | if (this.operations.from.length === 0 && !this.operations.fromRaw) { 375 | throw new Error('Cannot generate SQL, tableName is not defined'); 376 | } 377 | query += ` FROM ${this._processFrom( 378 | this.operations.from, 379 | this.operations.fromRaw 380 | )}`; 381 | 382 | query += this._processJoin(this.operations.join); 383 | 384 | const whereClauses = this.whereConditions.build(); 385 | if (whereClauses.length > 0) { 386 | query += ` WHERE ${whereClauses}`; 387 | } 388 | 389 | if (this.operations.groupBy.size > 0) { 390 | query += ` GROUP BY ${this._processGroupBy(this.operations.groupBy)}`; 391 | } 392 | 393 | if (this.operations.orderBy.length > 0) { 394 | query += ` ORDER BY ${this._processOrder(this.operations.orderBy)}`; 395 | } 396 | 397 | if (this.operations.limit) { 398 | query += ` LIMIT ${this.makeParamValue(this.operations.limit)}`; 399 | } 400 | 401 | if (this.operations.offset) { 402 | query += ` OFFSET ${this.makeParamValue(this.operations.offset)}`; 403 | } 404 | 405 | return query; 406 | } 407 | } 408 | 409 | function selectSubqueryBuilder(subquery, builder) { 410 | return typeof subquery === 'function' 411 | ? subquery(new SelectBuilder(builder.params, builder.options)) 412 | : subquery; 413 | } 414 | 415 | function buildSqlOrBuilder(value, valueMapper) { 416 | // Wrap builder but not string. 417 | if (typeof value === 'string') { 418 | return value; 419 | } 420 | const sql = valueMapper ? valueMapper(value).build() : value.build(); 421 | return `(${sql})`; 422 | } 423 | 424 | function processWith(withItems, whereValueMapper) { 425 | if (withItems.length === 0) return ''; 426 | return ( 427 | 'WITH ' + 428 | mapJoinIterable( 429 | withItems, 430 | (op) => { 431 | const query = 432 | typeof op.sql === 'string' 433 | ? op.sql 434 | : whereValueMapper(op.sql).build(); 435 | return `${op.alias} AS (${query})`; 436 | }, 437 | ', ' 438 | ) 439 | ); 440 | } 441 | 442 | module.exports = { 443 | SelectBuilder, 444 | selectSubqueryBuilder, 445 | buildSqlOrBuilder, 446 | processWith, 447 | }; 448 | -------------------------------------------------------------------------------- /lib/update-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { QueryConditionsBuilder } = require('./query-conditions-builder.js'); 4 | const { 5 | SelectBuilder, 6 | selectSubqueryBuilder, 7 | processWith, 8 | } = require('./select-builder.js'); 9 | const { makeKeyWithAlias } = require('./query-builder.js'); 10 | const { mapJoinIterable } = require('./utils.js'); 11 | const { buildSqlOrBuilder } = require('./select-builder'); 12 | 13 | class UpdateBuilder extends QueryConditionsBuilder { 14 | constructor(params, options) { 15 | super(params, options); 16 | this.operations = { 17 | table: null, 18 | from: [], 19 | fromSelect: null, 20 | set: [], 21 | with: [], 22 | returning: [], 23 | }; 24 | } 25 | 26 | select() { 27 | return new SelectBuilder(this.params, this.options); 28 | } 29 | 30 | table(tableName, alias) { 31 | const table = this.escapeIdentifier(tableName); 32 | this.operations.table = { table, alias: alias && this.escapeKey(alias) }; 33 | return this; 34 | } 35 | 36 | from(tableName, alias) { 37 | this.operations.from.push({ 38 | table: this.escapeIdentifier(tableName), 39 | alias: alias && this.escapeIdentifier(alias), 40 | }); 41 | return this; 42 | } 43 | 44 | fromSelect(sql, alias) { 45 | this.operations.fromSelect = { 46 | sql: selectSubqueryBuilder(sql, this), 47 | alias: alias && this.escapeIdentifier(alias), 48 | }; 49 | return this; 50 | } 51 | 52 | with(alias, sql) { 53 | this.operations.with.push({ sql, alias: this.makeKeyOrExpr(alias) }); 54 | return this; 55 | } 56 | 57 | returning(key, alias) { 58 | this.operations.returning.push({ 59 | key, 60 | alias: alias && this.escapeIdentifier(alias), 61 | }); 62 | return this; 63 | } 64 | 65 | set(column, value) { 66 | this.operations.set.push({ 67 | column: this.escapeKey(column), 68 | value: this._whereValueMapper(value), 69 | }); 70 | return this; 71 | } 72 | 73 | sets(obj) { 74 | for (const [key, value] of Object.entries(obj)) { 75 | if (value !== undefined) { 76 | this.set(key, value); 77 | } 78 | } 79 | return this; 80 | } 81 | 82 | setKey(column, key) { 83 | this.operations.set.push({ 84 | column: this.escapeKey(column), 85 | key: this.escapeKey(key), 86 | }); 87 | return this; 88 | } 89 | 90 | isNotEmpty() { 91 | return this.operations.set.length > 0; 92 | } 93 | 94 | // #private 95 | _whereValueMapper(subquery) { 96 | return selectSubqueryBuilder(subquery, this); 97 | } 98 | 99 | // #private 100 | _processFrom(fromSelect, from) { 101 | if (fromSelect) { 102 | const sql = buildSqlOrBuilder( 103 | fromSelect.sql, 104 | this._whereValueMapper.bind(this) 105 | ); 106 | return fromSelect.alias ? `${sql} AS ${fromSelect.alias}` : `${sql}`; 107 | } 108 | return mapJoinIterable(from, makeKeyWithAlias, ', '); 109 | } 110 | 111 | // #private 112 | _processSet(set) { 113 | return mapJoinIterable( 114 | set, 115 | (s) => `${s.column} = ${s.key ? s.key : this.makeParamValue(s.value)}`, 116 | ', ' 117 | ); 118 | } 119 | 120 | // #private 121 | _processReturning(returning) { 122 | if (returning[0]?.key === '*') return '*'; 123 | return mapJoinIterable( 124 | returning, 125 | (r) => this.makeKeyOrExpr(r.key) + (r.alias ? ` AS ${r.alias}` : ''), 126 | ', ' 127 | ); 128 | } 129 | 130 | build() { 131 | let query = ''; 132 | 133 | if (this.operations.with.length > 0) { 134 | query += 135 | processWith(this.operations.with, this._whereValueMapper.bind(this)) + 136 | ' '; 137 | } 138 | 139 | query += 'UPDATE '; 140 | 141 | if (!this.operations.table) { 142 | throw new Error('Cannot generate SQL, tableName is not defined'); 143 | } 144 | 145 | query += makeKeyWithAlias(this.operations.table); 146 | 147 | query += ` SET ${this._processSet(this.operations.set)}`; 148 | 149 | if (this.operations.fromSelect || this.operations.from?.length > 0) { 150 | query += ` FROM ${this._processFrom( 151 | this.operations.fromSelect, 152 | this.operations.from 153 | )}`; 154 | } 155 | 156 | const whereClauses = this.whereConditions.build(); 157 | if (whereClauses.length > 0) { 158 | query += ` WHERE ${whereClauses}`; 159 | } 160 | 161 | const { returning } = this.operations; 162 | if (returning?.length > 0) { 163 | query += ` RETURNING ${this._processReturning(returning)}`; 164 | } 165 | 166 | return query; 167 | } 168 | } 169 | 170 | module.exports = { UpdateBuilder }; 171 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const escapeIdentifier = (name) => { 4 | if (!name) return name; 5 | if (name.startsWith('"') && name.endsWith('"')) return name; 6 | return `"${name}"`; 7 | }; 8 | 9 | const pgEscapeIdentifier = (name) => { 10 | if (!name || typeof name !== 'string') return name; 11 | const castPos = name.indexOf('::'); 12 | if (castPos === -1) { 13 | return escapeIdentifier(name); 14 | } 15 | return escapeIdentifier(name.slice(0, castPos)) + name.slice(castPos); 16 | }; 17 | 18 | const escapeKey = (key, escapeIdentifier) => 19 | key && 20 | key 21 | .split('.') 22 | .map((k) => (k === '*' ? '*' : escapeIdentifier(k))) 23 | .join('.'); 24 | 25 | const mapJoinIterable = (val, mapper, sep) => { 26 | const it = val[Symbol.iterator](); 27 | const { done, value } = it.next(); 28 | if (done) return ''; 29 | let res = mapper(value); 30 | for (const value of it) { 31 | res += sep + mapper(value); 32 | } 33 | return res; 34 | }; 35 | 36 | const joinIterable = (val, sep) => { 37 | const it = val[Symbol.iterator](); 38 | const { done, value } = it.next(); 39 | if (done) return ''; 40 | let res = String(value); 41 | for (const value of it) { 42 | res += sep + value; 43 | } 44 | return res; 45 | }; 46 | 47 | module.exports = { 48 | escapeIdentifier, 49 | pgEscapeIdentifier, 50 | escapeKey, 51 | mapJoinIterable, 52 | joinIterable, 53 | }; 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metarhia/sql", 3 | "version": "0.2.10", 4 | "description": "Metarhia SQL utilities", 5 | "main": "sql.js", 6 | "types": "types/sql.d.ts", 7 | "files": [ 8 | "lib/", 9 | "types/" 10 | ], 11 | "scripts": { 12 | "lint": "eslint . && remark . && prettier --check \"**/*.js\" \"**/*.json\" \"**/*.md\" \"**/.*rc\" \"**/*.yml\" \"**/*.ts\"", 13 | "fmt": "prettier --write \"**/*.js\" \"**/*.json\" \"**/*.md\" \"**/.*rc\" \"**/*.yml\" \"**/*.ts\"", 14 | "fix": "eslint --fix . && npm run -s fmt", 15 | "types": "tsc -p types/tsconfig.json", 16 | "test": "npm run -s lint && npm run -s types && npm run -s test:only", 17 | "test:only": "metatests test/", 18 | "doc": "metadoc -c .metadocrc && npm run fmt" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/metarhia/sql.git" 23 | }, 24 | "keywords": [ 25 | "SQL", 26 | "Metarhia" 27 | ], 28 | "author": "Denys Otrishko ", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/metarhia/sql/issues" 32 | }, 33 | "homepage": "https://github.com/metarhia/sql#readme", 34 | "devDependencies": { 35 | "@commitlint/cli": "^17.6.7", 36 | "@commitlint/config-conventional": "^17.6.7", 37 | "@metarhia/doc": "^0.7.0", 38 | "@typescript-eslint/eslint-plugin": "^6.2.0", 39 | "@typescript-eslint/parser": "^6.2.0", 40 | "eslint": "^8.45.0", 41 | "eslint-config-metarhia": "^8.2.1", 42 | "eslint-config-prettier": "^8.8.0", 43 | "eslint-plugin-import": "^2.27.5", 44 | "eslint-plugin-prettier": "^5.0.0", 45 | "husky": "^8.0.3", 46 | "metatests": "^0.8.2", 47 | "prettier": "^3.0.0", 48 | "remark-cli": "^11.0.0", 49 | "remark-preset-lint-metarhia": "^2.0.1", 50 | "remark-validate-links": "^12.1.1", 51 | "typescript": "^5.1.6" 52 | }, 53 | "dependencies": { 54 | "@metarhia/iterator": "^1.0.0-alpha3" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sql.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { QueryBuilder, RawBuilder } = require('./lib/query-builder.js'); 4 | const { QueryConditionsBuilder } = require('./lib/query-conditions-builder.js'); 5 | const { SelectBuilder } = require('./lib/select-builder.js'); 6 | const { UpdateBuilder } = require('./lib/update-builder.js'); 7 | const { DeleteBuilder } = require('./lib/delete-builder.js'); 8 | const { InsertBuilder } = require('./lib/insert-builder.js'); 9 | const { PgInsertBuilder } = require('./lib/pg-insert-builder.js'); 10 | const { PgSelectBuilder } = require('./lib/pg-select-builder.js'); 11 | const { ConditionsBuilder } = require('./lib/conditions-builder.js'); 12 | const { ParamsBuilder } = require('./lib/params-builder.js'); 13 | const { PostgresParamsBuilder } = require('./lib/pg-params-builder.js'); 14 | const pg = require('./lib/pg.js'); 15 | const utils = require('./lib/utils'); 16 | 17 | module.exports = { 18 | QueryBuilder, 19 | QueryConditionsBuilder, 20 | SelectBuilder, 21 | RawBuilder, 22 | UpdateBuilder, 23 | DeleteBuilder, 24 | InsertBuilder, 25 | PgInsertBuilder, 26 | PgSelectBuilder, 27 | ConditionsBuilder, 28 | ParamsBuilder, 29 | PostgresParamsBuilder, 30 | ...pg, 31 | ...utils, 32 | }; 33 | -------------------------------------------------------------------------------- /test/conditions-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { SelectBuilder } = require('../lib/select-builder'); 4 | const { testSync } = require('metatests'); 5 | const { RawBuilder } = require('../lib/query-builder.js'); 6 | const { ConditionsBuilder } = require('../lib/conditions-builder'); 7 | const { PostgresParamsBuilder } = require('../lib/pg-params-builder'); 8 | 9 | const test = testSync('Conditions tests', null, { parallelSubtests: true }); 10 | test.beforeEach((test, callback) => { 11 | const params = new PostgresParamsBuilder(); 12 | callback({ builder: new ConditionsBuilder(params), params }); 13 | }); 14 | 15 | const allowedConditions = new Set([ 16 | '=', 17 | '!=', 18 | '<>', 19 | '<', 20 | '<=', 21 | '>', 22 | '>=', 23 | 'LIKE', 24 | 'EXISTS', 25 | 'IS', 26 | 'IS DISTINCT', 27 | 'IN', 28 | 'NOT IN', 29 | 'BETWEEN', 30 | 'BETWEEN SYMMETRIC', 31 | 'NOT BETWEEN', 32 | 'NOT BETWEEN SYMMETRIC', 33 | ]); 34 | 35 | test.testSync('single condition and', (test, { builder, params }) => { 36 | builder.and('f1', '=', 3); 37 | test.strictSame(builder.build(), '"f1" = $1'); 38 | test.strictSame(params.build(), [3]); 39 | }); 40 | 41 | test.testSync('single condition or', (test, { builder, params }) => { 42 | builder.or('f1', '=', 3); 43 | test.strictSame(builder.build(), '"f1" = $1'); 44 | test.strictSame(params.build(), [3]); 45 | }); 46 | 47 | test.testSync('simple .and().or() condition', (test, { builder, params }) => { 48 | builder.and('f1', '=', 3).or('f2', '=', 4); 49 | test.strictSame(builder.build(), '"f1" = $1 OR "f2" = $2'); 50 | test.strictSame(params.build(), [3, 4]); 51 | }); 52 | 53 | test.testSync('single condition not', (test, { builder, params }) => { 54 | builder.not('f1', '=', 3); 55 | test.strictSame(builder.build(), 'NOT "f1" = $1'); 56 | test.strictSame(params.build(), [3]); 57 | }); 58 | 59 | test.testSync('single condition orNot', (test, { builder, params }) => { 60 | builder.orNot('f1', '=', 3); 61 | test.strictSame(builder.build(), 'NOT "f1" = $1'); 62 | test.strictSame(params.build(), [3]); 63 | }); 64 | 65 | test.testSync('not with orNot condition', (test, { builder, params }) => { 66 | builder.not('f1', '=', 3).orNot('f2', '=', 42); 67 | test.strictSame(builder.build(), 'NOT "f1" = $1 OR NOT "f2" = $2'); 68 | test.strictSame(params.build(), [3, 42]); 69 | }); 70 | 71 | test.testSync('multiple conditions and', (test, { builder, params }) => { 72 | builder.and('f1', '=', 3).and('f2', '<', 'abc'); 73 | test.strictSame(builder.build(), '"f1" = $1 AND "f2" < $2'); 74 | test.strictSame(params.build(), [3, 'abc']); 75 | }); 76 | 77 | test.testSync('multiple conditions or', (test, { builder, params }) => { 78 | builder.or('f1', '=', 3).or('f2', '<', 'abc'); 79 | test.strictSame(builder.build(), '"f1" = $1 OR "f2" < $2'); 80 | test.strictSame(params.build(), [3, 'abc']); 81 | }); 82 | 83 | test.testSync('condition date', (test, { builder, params }) => { 84 | const date = new Date(1537025908018); // 2018-09-15T15:38:28.018Z 85 | builder.and('f2', '=', date); 86 | test.strictSame(builder.build(), '"f2" = $1'); 87 | test.strictSame(params.build(), [date]); 88 | }); 89 | 90 | test.testSync('condition null', (test, { builder, params }) => { 91 | builder.null('f1'); 92 | test.strictSame(builder.build(), '"f1" IS NULL'); 93 | test.strictSame(params.build(), []); 94 | }); 95 | 96 | test.testSync('condition orNull', (test, { builder, params }) => { 97 | builder.orNull('f1'); 98 | test.strictSame(builder.build(), '"f1" IS NULL'); 99 | test.strictSame(params.build(), []); 100 | }); 101 | 102 | test.testSync('condition null and orNull', (test, { builder, params }) => { 103 | builder.null('f1').orNull('f2'); 104 | test.strictSame(builder.build(), '"f1" IS NULL OR "f2" IS NULL'); 105 | test.strictSame(params.build(), []); 106 | }); 107 | 108 | test.testSync('condition notNull', (test, { builder, params }) => { 109 | builder.notNull('f1'); 110 | test.strictSame(builder.build(), '"f1" IS NOT NULL'); 111 | test.strictSame(params.build(), []); 112 | }); 113 | 114 | test.testSync('condition orNotNull', (test, { builder, params }) => { 115 | builder.orNotNull('f1'); 116 | test.strictSame(builder.build(), '"f1" IS NOT NULL'); 117 | test.strictSame(params.build(), []); 118 | }); 119 | 120 | test.testSync( 121 | 'condition notNull and orNotNull', 122 | (test, { builder, params }) => { 123 | builder.notNull('f1').orNotNull('f2'); 124 | test.strictSame(builder.build(), '"f1" IS NOT NULL OR "f2" IS NOT NULL'); 125 | test.strictSame(params.build(), []); 126 | } 127 | ); 128 | 129 | test.testSync('condition in numbers', (test, { builder, params }) => { 130 | builder.in('f1', [1, 2, 3]); 131 | test.strictSame(builder.build(), '"f1" IN ($1, $2, $3)'); 132 | test.strictSame(params.build(), [1, 2, 3]); 133 | }); 134 | 135 | test.testSync('condition orIn numbers', (test, { builder, params }) => { 136 | builder.orIn('f1', [1, 2, 3]); 137 | test.strictSame(builder.build(), '"f1" IN ($1, $2, $3)'); 138 | test.strictSame(params.build(), [1, 2, 3]); 139 | }); 140 | 141 | test.testSync( 142 | 'condition in numbers or numbers', 143 | (test, { builder, params }) => { 144 | builder.in('f1', [1, 2, 3]).orIn('f2', [1, 2, 3]); 145 | test.strictSame( 146 | builder.build(), 147 | '"f1" IN ($1, $2, $3) OR "f2" IN ($4, $5, $6)' 148 | ); 149 | test.strictSame(params.build(), [1, 2, 3, 1, 2, 3]); 150 | } 151 | ); 152 | 153 | test.testSync('condition in set', (test, { builder, params }) => { 154 | builder.in('f1', new Set([1, 2, 3])); 155 | test.strictSame(builder.build(), '"f1" IN ($1, $2, $3)'); 156 | test.strictSame(params.build(), [1, 2, 3]); 157 | }); 158 | 159 | test.testSync('condition orIn set', (test, { builder, params }) => { 160 | builder.orIn('f1', new Set([1, 2, 3])); 161 | test.strictSame(builder.build(), '"f1" IN ($1, $2, $3)'); 162 | test.strictSame(params.build(), [1, 2, 3]); 163 | }); 164 | 165 | test.testSync('condition in set or array', (test, { builder, params }) => { 166 | builder.in('f1', new Set([1, 2, 3])).orIn('f2', new Set([1, 2, 3])); 167 | test.strictSame( 168 | builder.build(), 169 | '"f1" IN ($1, $2, $3) OR "f2" IN ($4, $5, $6)' 170 | ); 171 | test.strictSame(params.build(), [1, 2, 3, 1, 2, 3]); 172 | }); 173 | 174 | test.testSync('condition notIn numbers', (test, { builder, params }) => { 175 | builder.notIn('f1', [1, 2, 3]); 176 | test.strictSame(builder.build(), '"f1" NOT IN ($1, $2, $3)'); 177 | test.strictSame(params.build(), [1, 2, 3]); 178 | }); 179 | 180 | test.testSync('condition any numbers', (test, { builder, params }) => { 181 | builder.any('f1', [1, 2, 3]); 182 | test.strictSame(builder.build(), '"f1" = ANY ($1)'); 183 | test.strictSame(params.build(), [[1, 2, 3]]); 184 | }); 185 | 186 | test.testSync( 187 | 'condition any numbers or numbers', 188 | (test, { builder, params }) => { 189 | builder.any('f1', [1, 2, 3]).orAny('f2', [3, 2, 1]); 190 | test.strictSame(builder.build(), '"f1" = ANY ($1) OR "f2" = ANY ($2)'); 191 | test.strictSame(params.build(), [ 192 | [1, 2, 3], 193 | [3, 2, 1], 194 | ]); 195 | } 196 | ); 197 | 198 | test.testSync('condition like', (test, { builder, params }) => { 199 | builder.and('f1', 'like', 'abc'); 200 | test.strictSame(builder.build(), '"f1" LIKE $1'); 201 | test.strictSame(params.build(), ['abc']); 202 | }); 203 | 204 | test.testSync('condition ilike', (test, { builder, params }) => { 205 | builder.and('f1', 'ilike', 'abc'); 206 | test.strictSame(builder.build(), '"f1" ILIKE $1'); 207 | test.strictSame(params.build(), ['abc']); 208 | }); 209 | 210 | test.testSync('condition shortcut like', (test, { builder, params }) => { 211 | builder.like('f1', 'abc'); 212 | test.strictSame(builder.build(), '"f1" LIKE $1'); 213 | test.strictSame(params.build(), ['abc']); 214 | }); 215 | 216 | test.testSync('condition shortcut not like', (test, { builder, params }) => { 217 | builder.notLike('f1', 'abc'); 218 | test.strictSame(builder.build(), '"f1" NOT LIKE $1'); 219 | test.strictSame(params.build(), ['abc']); 220 | }); 221 | 222 | test.testSync('condition shortcut or not like', (test, { builder, params }) => { 223 | builder.orLike('f1', 'abc').orNotLike('f2', 'abc'); 224 | test.strictSame(builder.build(), '"f1" LIKE $1 OR "f2" NOT LIKE $2'); 225 | test.strictSame(params.build(), ['abc', 'abc']); 226 | }); 227 | 228 | test.testSync('condition shortcut ilike', (test, { builder, params }) => { 229 | builder.ilike('f1', 'abc'); 230 | test.strictSame(builder.build(), '"f1" ILIKE $1'); 231 | test.strictSame(params.build(), ['abc']); 232 | }); 233 | 234 | test.testSync('condition shortcut not ilike', (test, { builder, params }) => { 235 | builder.notILike('f1', 'abc'); 236 | test.strictSame(builder.build(), '"f1" NOT ILIKE $1'); 237 | test.strictSame(params.build(), ['abc']); 238 | }); 239 | 240 | test.testSync( 241 | 'condition shortcut or not ilike', 242 | (test, { builder, params }) => { 243 | builder.orILike('f1', 'abc').orNotILike('f2', 'abc'); 244 | test.strictSame(builder.build(), '"f1" ILIKE $1 OR "f2" NOT ILIKE $2'); 245 | test.strictSame(params.build(), ['abc', 'abc']); 246 | } 247 | ); 248 | 249 | test.testSync('condition or like', (test, { builder, params }) => { 250 | builder.or('f1', 'like', 'abc'); 251 | test.strictSame(builder.build(), '"f1" LIKE $1'); 252 | test.strictSame(params.build(), ['abc']); 253 | }); 254 | 255 | test.testSync('condition or like key', (test, { builder, params }) => { 256 | builder.or('f1', 'like', builder.key('f2')); 257 | test.strictSame(builder.build(), '"f1" LIKE ("f2")'); 258 | test.strictSame(params.build(), []); 259 | }); 260 | 261 | test.testSync('condition like or like', (test, { builder, params }) => { 262 | builder.and('f1', 'like', 'abc').or('f2', 'like', 'cba'); 263 | test.strictSame(builder.build(), '"f1" LIKE $1 OR "f2" LIKE $2'); 264 | test.strictSame(params.build(), ['abc', 'cba']); 265 | }); 266 | 267 | test.testSync('allowed conditions and', (test, { builder }) => { 268 | // Must not throw. 269 | allowedConditions.forEach((cond) => builder.and('f1', cond, 42)); 270 | }); 271 | 272 | test.testSync('allowed conditions or', (test, { builder }) => { 273 | // Must not throw. 274 | allowedConditions.forEach((cond) => builder.or('f1', cond, 42)); 275 | }); 276 | 277 | test.testSync('condition !=', (test, { builder, params }) => { 278 | builder.and('f1', '!=', 'abc'); 279 | test.strictSame(builder.build(), '"f1" <> $1'); 280 | test.strictSame(params.build(), ['abc']); 281 | }); 282 | 283 | test.testSync('condition != key', (test, { builder, params }) => { 284 | builder.and('f1', '!=', builder.key('f2')); 285 | test.strictSame(builder.build(), '"f1" <> ("f2")'); 286 | test.strictSame(params.build(), []); 287 | }); 288 | 289 | test.testSync('condition or !=', (test, { builder, params }) => { 290 | builder.or('f1', '!=', 'abc'); 291 | test.strictSame(builder.build(), '"f1" <> $1'); 292 | test.strictSame(params.build(), ['abc']); 293 | }); 294 | 295 | test.testSync( 296 | 'qualified rows with multiple conditions', 297 | (test, { builder, params }) => { 298 | builder.and('table1.f', '=', 'abc').and('table2.f', '=', 'abc'); 299 | test.strictSame(builder.build(), '"table1"."f" = $1 AND "table2"."f" = $2'); 300 | test.strictSame(params.build(), ['abc', 'abc']); 301 | } 302 | ); 303 | 304 | test.testSync( 305 | 'qualified rows with multiple conditions or', 306 | (test, { builder, params }) => { 307 | builder 308 | .and('table1.f', '=', 'abc') 309 | .and('table2.f', '=', 'abc') 310 | .or('table3.f', '>', 13); 311 | test.strictSame( 312 | builder.build(), 313 | '"table1"."f" = $1 AND "table2"."f" = $2 OR "table3"."f" > $3' 314 | ); 315 | test.strictSame(params.build(), ['abc', 'abc', 13]); 316 | } 317 | ); 318 | 319 | test.testSync('condition nested', (test, { builder, params }) => { 320 | const nested = new SelectBuilder(builder.params) 321 | .from('table2') 322 | .select('A') 323 | .where('f1', '=', 42); 324 | builder.and('a', '=', nested); 325 | test.strictSame( 326 | builder.build(), 327 | '"a" = (SELECT "A" FROM "table2" WHERE "f1" = $1)' 328 | ); 329 | test.strictSame(params.build(), [42]); 330 | }); 331 | 332 | test.testSync('condition nested or', (test, { builder, params }) => { 333 | const nested = new SelectBuilder(builder.params) 334 | .from('table2') 335 | .select('A') 336 | .where('f1', '=', 42); 337 | builder.or('a', '=', nested); 338 | test.strictSame( 339 | builder.build(), 340 | '"a" = (SELECT "A" FROM "table2" WHERE "f1" = $1)' 341 | ); 342 | test.strictSame(params.build(), [42]); 343 | }); 344 | 345 | test.testSync('condition nested raw', (test, { builder, params }) => { 346 | const nested = new RawBuilder( 347 | (params) => `SELECT "A" FROM "table2" WHERE "f1" = ${params.add(42)}`, 348 | params 349 | ); 350 | builder.and('f1', '=', nested); 351 | test.strictSame( 352 | builder.build(), 353 | '"f1" = (SELECT "A" FROM "table2" WHERE "f1" = $1)' 354 | ); 355 | test.strictSame(params.build(), [42]); 356 | }); 357 | 358 | test.testSync('condition nested raw or select', (test, { builder, params }) => { 359 | const nestedAnd = new RawBuilder( 360 | (params) => `SELECT "A" FROM "table2" WHERE "f1" = ${params.add(42)}`, 361 | params 362 | ); 363 | const nestedOr = new SelectBuilder(builder.params) 364 | .from('table2') 365 | .select('A') 366 | .where('f1', '=', 42); 367 | builder.and('f1', '=', nestedAnd).or('f2', '=', nestedOr); 368 | const expectedSql = `"f1" = (SELECT "A" FROM "table2" WHERE "f1" = $1) 369 | OR "f2" = (SELECT "A" FROM "table2" WHERE "f1" = $2)`; 370 | test.strictSame(builder.build(), expectedSql.replace(/\n\s+/g, ' ')); 371 | test.strictSame(params.build(), [42, 42]); 372 | }); 373 | 374 | test.testSync('condition exists', (test, { builder, params }) => { 375 | const nested = new SelectBuilder(params).from('table2').where('f1', '=', 42); 376 | builder.exists(nested).and('f1', '=', 13); 377 | test.strictSame( 378 | builder.build(), 379 | 'EXISTS (SELECT * FROM "table2" WHERE "f1" = $1) AND "f1" = $2' 380 | ); 381 | test.strictSame(params.build(), [42, 13]); 382 | }); 383 | 384 | test.testSync('condition orExists', (test, { builder, params }) => { 385 | const nested = new SelectBuilder(params).from('table2').where('f1', '=', 42); 386 | builder.orExists(nested).and('f1', '=', 13); 387 | test.strictSame( 388 | builder.build(), 389 | 'EXISTS (SELECT * FROM "table2" WHERE "f1" = $1) AND "f1" = $2' 390 | ); 391 | test.strictSame(params.build(), [42, 13]); 392 | }); 393 | 394 | test.testSync('conditions between', (test, { builder, params }) => { 395 | builder 396 | .between('a', 1, 100) 397 | .between('b', 100, 1, true) 398 | .between('c', 'aaa', 'yyy'); 399 | test.strictSame( 400 | builder.build(), 401 | `"a" BETWEEN $1 AND $2 402 | AND "b" BETWEEN SYMMETRIC $3 AND $4 403 | AND "c" BETWEEN $5 AND $6`.replace(/\n\s+/g, ' ') 404 | ); 405 | test.strictSame(params.build(), [1, 100, 100, 1, 'aaa', 'yyy']); 406 | }); 407 | 408 | test.testSync('conditions orBetween', (test, { builder, params }) => { 409 | builder 410 | .orBetween('a', 1, 100) 411 | .orBetween('b', 100, 1, true) 412 | .orBetween('c', 'aaa', 'yyy'); 413 | test.strictSame( 414 | builder.build(), 415 | `"a" BETWEEN $1 AND $2 416 | OR "b" BETWEEN SYMMETRIC $3 AND $4 417 | OR "c" BETWEEN $5 AND $6`.replace(/\n\s+/g, ' ') 418 | ); 419 | test.strictSame(params.build(), [1, 100, 100, 1, 'aaa', 'yyy']); 420 | }); 421 | 422 | test.testSync('conditions notBetween', (test, { builder, params }) => { 423 | builder.notBetween('a', 1, 100).notBetween('b', 'aaa', 'yyy', true); 424 | test.strictSame( 425 | builder.build(), 426 | `"a" NOT BETWEEN $1 AND $2 427 | AND "b" NOT BETWEEN SYMMETRIC $3 AND $4`.replace(/\n\s+/g, ' ') 428 | ); 429 | test.strictSame(params.build(), [1, 100, 'aaa', 'yyy']); 430 | }); 431 | 432 | test.testSync('conditions orNotBetween', (test, { builder, params }) => { 433 | builder.orNotBetween('a', 1, 100).orNotBetween('b', 'aaa', 'yyy', true); 434 | test.strictSame( 435 | builder.build(), 436 | `"a" NOT BETWEEN $1 AND $2 437 | OR "b" NOT BETWEEN SYMMETRIC $3 AND $4`.replace(/\n\s+/g, ' ') 438 | ); 439 | test.strictSame(params.build(), [1, 100, 'aaa', 'yyy']); 440 | }); 441 | 442 | test.testSync('conditions between multiple', (test, { builder, params }) => { 443 | builder 444 | .notBetween('a', 1, 100) 445 | .orBetween('c', 1, 100) 446 | .between('b', 'xzy', 'yyy', true) 447 | .notBetween('c', 'aaa', 'yyy', true) 448 | .between('d', 42, 100) 449 | .orNotBetween('e', 123, 222); 450 | test.strictSame( 451 | builder.build(), 452 | `"a" NOT BETWEEN $1 AND $2 453 | OR "c" BETWEEN $3 AND $4 454 | AND "b" BETWEEN SYMMETRIC $5 AND $6 455 | AND "c" NOT BETWEEN SYMMETRIC $7 AND $8 456 | AND "d" BETWEEN $9 AND $10 457 | OR "e" NOT BETWEEN $11 AND $12`.replace(/\n\s+/g, ' ') 458 | ); 459 | const args = [1, 100, 1, 100, 'xzy', 'yyy', 'aaa', 'yyy', 42, 100, 123, 222]; 460 | test.strictSame(params.build(), args); 461 | }); 462 | 463 | test.testSync('condition in nested', (test, { builder, params }) => { 464 | const nestedQuery = new SelectBuilder(params); 465 | nestedQuery.from('table2').select('a').where('id', '>', 42); 466 | 467 | builder.in('a', nestedQuery); 468 | test.strictSame( 469 | builder.build(), 470 | `"a" IN (SELECT "a" FROM "table2" WHERE "id" > $1)` 471 | ); 472 | test.strictSame(params.build(), [42]); 473 | }); 474 | 475 | test.testSync('condition orIn nested', (test, { builder, params }) => { 476 | const nestedQuery = new SelectBuilder(params); 477 | nestedQuery.from('table2').select('a').where('id', '>', 42); 478 | 479 | builder.orIn('a', nestedQuery); 480 | test.strictSame( 481 | builder.build(), 482 | `"a" IN (SELECT "a" FROM "table2" WHERE "id" > $1)` 483 | ); 484 | test.strictSame(params.build(), [42]); 485 | }); 486 | 487 | test.testSync('condition in and orIn nested', (test, { builder, params }) => { 488 | const nestedAnd = new SelectBuilder(params) 489 | .from('table2') 490 | .select('a') 491 | .where('id', '>', 42); 492 | const nestedOr = new SelectBuilder(params) 493 | .from('table3') 494 | .select('b') 495 | .where('r2', '>', 24); 496 | 497 | builder.in('a', nestedAnd).orIn('b', nestedOr); 498 | const expectedSql = `"a" IN (SELECT "a" FROM "table2" WHERE "id" > $1) 499 | OR "b" IN (SELECT "b" FROM "table3" WHERE "r2" > $2)`; 500 | test.strictSame(builder.build(), expectedSql.replace(/\n\s+/g, ' ')); 501 | test.strictSame(params.build(), [42, 24]); 502 | }); 503 | 504 | test.testSync('condition notIn nested', (test, { builder, params }) => { 505 | const nestedQuery = new SelectBuilder(params); 506 | nestedQuery.from('table2').select('a').where('id', '>', 42); 507 | 508 | builder.notIn('a', nestedQuery); 509 | test.strictSame( 510 | builder.build(), 511 | `"a" NOT IN (SELECT "a" FROM "table2" WHERE "id" > $1)` 512 | ); 513 | test.strictSame(params.build(), [42]); 514 | }); 515 | 516 | test.testSync('condition orNotIn nested', (test, { builder, params }) => { 517 | const nestedQuery = new SelectBuilder(params); 518 | nestedQuery.from('table2').select('a').where('id', '>', 42); 519 | 520 | builder.orNotIn('a', nestedQuery); 521 | test.strictSame( 522 | builder.build(), 523 | `"a" NOT IN (SELECT "a" FROM "table2" WHERE "id" > $1)` 524 | ); 525 | test.strictSame(params.build(), [42]); 526 | }); 527 | 528 | test.testSync('condition any nested', (test, { builder, params }) => { 529 | const nestedQuery = new SelectBuilder(params); 530 | nestedQuery.from('table2').select('a').where('id', '>', 42); 531 | 532 | builder.any('a', nestedQuery); 533 | test.strictSame( 534 | builder.build(), 535 | `"a" = ANY (SELECT "a" FROM "table2" WHERE "id" > $1)` 536 | ); 537 | test.strictSame(params.build(), [42]); 538 | }); 539 | 540 | test.testSync('condition orAny nested', (test, { builder, params }) => { 541 | const nestedQuery = new SelectBuilder(params); 542 | nestedQuery.from('table2').select('a').where('id', '>', 42); 543 | 544 | builder.orAny('a', nestedQuery); 545 | test.strictSame( 546 | builder.build(), 547 | `"a" = ANY (SELECT "a" FROM "table2" WHERE "id" > $1)` 548 | ); 549 | test.strictSame(params.build(), [42]); 550 | }); 551 | 552 | test.testSync( 553 | 'condition between nested simple', 554 | (test, { builder, params }) => { 555 | const nestedStart = new RawBuilder(() => 'SELECT -1', params); 556 | const nestedEnd = new RawBuilder(() => 'SELECT 42', params); 557 | builder 558 | .between('a', nestedStart, nestedEnd) 559 | .between('b', nestedEnd, nestedStart, true); 560 | const expectedSql = `"a" BETWEEN (SELECT -1) AND (SELECT 42) 561 | AND "b" BETWEEN SYMMETRIC (SELECT 42) AND (SELECT -1)`; 562 | test.strictSame(builder.build(), expectedSql.replace(/\n\s+/g, ' ')); 563 | test.strictSame(params.build(), []); 564 | } 565 | ); 566 | 567 | test.testSync( 568 | 'condition between nested complex', 569 | (test, { builder, params }) => { 570 | const nestedStart = new SelectBuilder(params) 571 | .from('table2') 572 | .select('f1') 573 | .where('f2', '>', 42) 574 | .limit(1); 575 | const nestedEnd = new SelectBuilder(params) 576 | .from('table3') 577 | .select('f1') 578 | .where('f2', '<', 42) 579 | .limit(1); 580 | builder 581 | .between('a', nestedStart, nestedEnd) 582 | .between('b', nestedEnd, nestedStart, true); 583 | const expectedSql = `"a" BETWEEN 584 | (SELECT "f1" FROM "table2" WHERE "f2" > $1 LIMIT $2) AND 585 | (SELECT "f1" FROM "table3" WHERE "f2" < $3 LIMIT $4) 586 | AND "b" BETWEEN SYMMETRIC 587 | (SELECT "f1" FROM "table3" WHERE "f2" < $5 LIMIT $6) AND 588 | (SELECT "f1" FROM "table2" WHERE "f2" > $7 LIMIT $8)`; 589 | test.strictSame(builder.build(), expectedSql.replace(/\n\s+/g, ' ')); 590 | test.strictSame(params.build(), [42, 1, 42, 1, 42, 1, 42, 1]); 591 | } 592 | ); 593 | 594 | test.testSync( 595 | 'nested conditions functions with or/and', 596 | (test, { builder, params }) => { 597 | builder 598 | .and('f1', '=', 42) 599 | .null('f2') 600 | .and((builder) => builder.or('a1', '=', 13).or('a2', '>', 42)) 601 | .or((builder) => builder.and('f3', '<', 97).between('f4', 100, 142)); 602 | const expectedSql = `"f1" = $1 AND "f2" IS NULL 603 | AND ("a1" = $2 OR "a2" > $3) 604 | OR ("f3" < $4 AND "f4" BETWEEN $5 AND $6)`; 605 | test.strictSame(builder.build(), expectedSql.replace(/\n\s+/g, ' ')); 606 | test.strictSame(params.build(), [42, 13, 42, 97, 100, 142]); 607 | } 608 | ); 609 | 610 | test.testSync( 611 | 'nested conditions query with or/and', 612 | (test, { builder, params }) => { 613 | const andQuery = new ConditionsBuilder(params) 614 | .or('a1', '=', 13) 615 | .or('a2', '>', 42); 616 | const orQuery = new ConditionsBuilder(params) 617 | .and('f3', '<', 97) 618 | .between('f4', 100, 142); 619 | builder 620 | .and('f1', '=', 42) 621 | .notNull('f2') 622 | .and(andQuery) 623 | .or(orQuery) 624 | .notBetween('f5', 42, 24, true); 625 | const expectedSql = `"f1" = $1 AND "f2" IS NOT NULL 626 | AND ("a1" = $2 OR "a2" > $3) 627 | OR ("f3" < $4 AND "f4" BETWEEN $5 AND $6) 628 | AND "f5" NOT BETWEEN SYMMETRIC $7 AND $8`; 629 | test.strictSame(builder.build(), expectedSql.replace(/\n\s+/g, ' ')); 630 | test.strictSame(params.build(), [42, 13, 42, 97, 100, 142, 42, 24]); 631 | } 632 | ); 633 | -------------------------------------------------------------------------------- /test/delete-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { testSync } = require('metatests'); 4 | const { PostgresParamsBuilder } = require('../lib/pg-params-builder'); 5 | const { DeleteBuilder } = require('../sql.js'); 6 | const { SelectBuilder } = require('../lib/select-builder.js'); 7 | 8 | const test = testSync('DeleteBuilder tests', null, { parallelSubtests: true }); 9 | 10 | test.beforeEach(async () => { 11 | const params = new PostgresParamsBuilder(); 12 | return { builder: new DeleteBuilder(params), params }; 13 | }); 14 | 15 | test.testSync('delete all items', (test, { builder, params }) => { 16 | builder.from('Table'); 17 | 18 | test.strictSame(builder.build(), 'DELETE FROM "Table"'); 19 | test.strictSame(params.build(), []); 20 | }); 21 | 22 | test.testSync('delete all items with alias', (test, { builder, params }) => { 23 | builder.from('Table', 'Alias'); 24 | 25 | test.strictSame(builder.build(), 'DELETE FROM "Table" AS "Alias"'); 26 | test.strictSame(params.build(), []); 27 | }); 28 | 29 | test.testSync('delete with > condition', (test, { builder, params }) => { 30 | builder.from('Table').where('a', '>', 42); 31 | 32 | test.strictSame(builder.build(), 'DELETE FROM "Table" WHERE "a" > $1'); 33 | test.strictSame(params.build(), [42]); 34 | }); 35 | 36 | test.testSync( 37 | 'delete with simple or condition', 38 | (test, { builder, params }) => { 39 | builder.from('Table').whereEq('a', '42').orWhere('b', '=', '24'); 40 | 41 | test.strictSame( 42 | builder.build(), 43 | 'DELETE FROM "Table" WHERE "a" = $1 OR "b" = $2' 44 | ); 45 | test.strictSame(params.build(), ['42', '24']); 46 | } 47 | ); 48 | 49 | test.testSync('delete with null and condition', (test, { builder, params }) => { 50 | builder.from('Table').whereMore('a', 999).whereNull('b'); 51 | 52 | test.strictSame( 53 | builder.build(), 54 | 'DELETE FROM "Table" WHERE "a" > $1 AND "b" IS NULL' 55 | ); 56 | test.strictSame(params.build(), [999]); 57 | }); 58 | 59 | test.testSync( 60 | 'delete with condition in nested', 61 | (test, { builder, params }) => { 62 | const nestedQuery = new SelectBuilder(params); 63 | nestedQuery.from('table2').select('a').where('id', '>', 42); 64 | 65 | builder.from('Table').whereIn('a', nestedQuery); 66 | 67 | test.strictSame( 68 | builder.build(), 69 | `DELETE FROM "Table" WHERE "a" IN (SELECT "a" FROM "table2" WHERE "id" > $1)` 70 | ); 71 | test.strictSame(params.build(), [42]); 72 | } 73 | ); 74 | 75 | test.testSync( 76 | 'delete with condition in nested builder', 77 | (test, { builder, params }) => { 78 | builder 79 | .from('Table') 80 | .whereIn('a', (b) => b.from('table2').select('a').where('id', '>', 42)); 81 | 82 | test.strictSame( 83 | builder.build(), 84 | `DELETE FROM "Table" WHERE "a" IN (SELECT "a" FROM "table2" WHERE "id" > $1)` 85 | ); 86 | test.strictSame(params.build(), [42]); 87 | } 88 | ); 89 | -------------------------------------------------------------------------------- /test/insert-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { testSync } = require('metatests'); 4 | const { 5 | PostgresParamsBuilder, 6 | SelectBuilder, 7 | InsertBuilder, 8 | } = require('../sql.js'); 9 | const { UpdateBuilder } = require('../lib/update-builder.js'); 10 | 11 | const test = testSync('InsertBuilder tests', null, { parallelSubtests: true }); 12 | 13 | test.beforeEach(async () => { 14 | const params = new PostgresParamsBuilder(); 15 | return { builder: new InsertBuilder(params), params }; 16 | }); 17 | 18 | test.testSync('insert multiple items', (test, { builder, params }) => { 19 | builder.table('Table').value('a', 42).value('b', 'aaa'); 20 | 21 | test.strictSame( 22 | builder.build(), 23 | 'INSERT INTO "Table" ("a", "b") VALUES ($1, $2)' 24 | ); 25 | test.strictSame(params.build(), [42, 'aaa']); 26 | }); 27 | 28 | test.testSync( 29 | 'insert multiple items wih alias', 30 | (test, { builder, params }) => { 31 | builder.table('Table', 't').value('a', 42).value('b', 'aaa'); 32 | 33 | test.strictSame( 34 | builder.build(), 35 | 'INSERT INTO "Table" AS "t" ("a", "b") VALUES ($1, $2)' 36 | ); 37 | test.strictSame(params.build(), [42, 'aaa']); 38 | } 39 | ); 40 | 41 | test.testSync('stringify insert object', (test, { builder, params }) => { 42 | const obj = { a: 'vv', b: true }; 43 | const date = new Date(); 44 | builder.table('Table').value('a', 42).value('b', obj).value('c', date); 45 | 46 | test.strictSame( 47 | builder.build(), 48 | 'INSERT INTO "Table" ("a", "b", "c") VALUES ($1, $2, $3)' 49 | ); 50 | test.strictSame(params.build(), [42, JSON.stringify(obj), date]); 51 | }); 52 | 53 | test.testSync('insert with query value', (test, { builder, params }) => { 54 | const nestedQuery = new SelectBuilder(params); 55 | nestedQuery.from('table2').select('a').where('id', '>', 42).limit(1); 56 | 57 | builder.table('Table').value('a', nestedQuery).value('b', false); 58 | 59 | test.strictSame( 60 | builder.build(), 61 | 'INSERT INTO "Table" ("a", "b") VALUES ((SELECT "a" FROM "table2" WHERE "id" > $1 LIMIT $2), $3)' 62 | ); 63 | test.strictSame(params.build(), [42, 1, false]); 64 | }); 65 | 66 | test.testSync('insert with nested builder', (test, { builder, params }) => { 67 | builder 68 | .table('Table') 69 | .value('a', (b) => 70 | b.from('table2').select('a').where('id', '>', 42).limit(1) 71 | ) 72 | .value('b', false); 73 | 74 | test.strictSame( 75 | builder.build(), 76 | 'INSERT INTO "Table" ("a", "b") VALUES ((SELECT "a" FROM "table2" WHERE "id" > $1 LIMIT $2), $3)' 77 | ); 78 | test.strictSame(params.build(), [42, 1, false]); 79 | }); 80 | 81 | test.testSync( 82 | 'insert multiple with nested builder', 83 | (test, { builder, params }) => { 84 | builder.table('Table').values({ 85 | a: (b) => b.from('table2').select('a').where('id', '>', 42).limit(1), 86 | b: false, 87 | }); 88 | 89 | test.strictSame( 90 | builder.build(), 91 | 'INSERT INTO "Table" ("a", "b") VALUES ((SELECT "a" FROM "table2" WHERE "id" > $1 LIMIT $2), $3)' 92 | ); 93 | test.strictSame(params.build(), [42, 1, false]); 94 | } 95 | ); 96 | 97 | test.testSync('insert with from nested update', (test, { builder, params }) => { 98 | builder 99 | .with( 100 | 'upd', 101 | new UpdateBuilder(builder.params) 102 | .table('employees') 103 | .set('sales_count', builder.raw('"sales_count" + 1')) 104 | .whereEq( 105 | 'id', 106 | builder 107 | .select() 108 | .from('accounts') 109 | .select('sales_person') 110 | .whereEq('name', 'Acme Corporation') 111 | ) 112 | .returning('*') 113 | ) 114 | .table('employees_log') 115 | .from( 116 | builder.select().from('upd').select('*').selectRaw('current_timestamp') 117 | ); 118 | 119 | test.strictSame( 120 | builder.build(), 121 | `WITH "upd" AS (UPDATE "employees" SET "sales_count" = ("sales_count" + 1) WHERE "id" = (SELECT "sales_person" FROM "accounts" WHERE "name" = $1) RETURNING *) INSERT INTO "employees_log" SELECT *, current_timestamp FROM "upd"` 122 | ); 123 | test.strictSame(params.build(), ['Acme Corporation']); 124 | }); 125 | -------------------------------------------------------------------------------- /test/pg-insert-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { testSync } = require('metatests'); 4 | const { 5 | PostgresParamsBuilder, 6 | PgInsertBuilder, 7 | RawBuilder, 8 | } = require('../sql.js'); 9 | 10 | const test = testSync('PgInsertBuilder tests', null, { 11 | parallelSubtests: true, 12 | }); 13 | 14 | test.beforeEach(async () => { 15 | const params = new PostgresParamsBuilder(); 16 | return { builder: new PgInsertBuilder(params), params }; 17 | }); 18 | 19 | test.testSync('insert multiple items', (test, { builder, params }) => { 20 | builder.table('Table').value('a', 42).value('b', 'aaa'); 21 | 22 | test.strictSame( 23 | builder.build(), 24 | 'INSERT INTO "Table" ("a", "b") VALUES ($1, $2)' 25 | ); 26 | test.strictSame(params.build(), [42, 'aaa']); 27 | }); 28 | 29 | test.testSync( 30 | 'insert multiple items with returning all', 31 | (test, { builder, params }) => { 32 | builder.table('Table').value('a', 42).value('b', 'aaa').returning('*'); 33 | 34 | test.strictSame( 35 | builder.build(), 36 | 'INSERT INTO "Table" ("a", "b") VALUES ($1, $2) RETURNING *' 37 | ); 38 | test.strictSame(params.build(), [42, 'aaa']); 39 | } 40 | ); 41 | 42 | test.testSync( 43 | 'insert multiple items with returning key', 44 | (test, { builder, params }) => { 45 | builder.table('Table').value('a', 42).value('b', 'aaa').returning('id'); 46 | 47 | test.strictSame( 48 | builder.build(), 49 | 'INSERT INTO "Table" ("a", "b") VALUES ($1, $2) RETURNING "id"' 50 | ); 51 | test.strictSame(params.build(), [42, 'aaa']); 52 | } 53 | ); 54 | 55 | test.testSync( 56 | 'insert multiple items with returning multiple keys', 57 | (test, { builder, params }) => { 58 | builder 59 | .table('Table') 60 | .value('a', 42) 61 | .value('b', 'aaa') 62 | .returning('id') 63 | .returning('f1') 64 | .returning('f2'); 65 | 66 | test.strictSame( 67 | builder.build(), 68 | 'INSERT INTO "Table" ("a", "b") VALUES ($1, $2) RETURNING "id", "f1", "f2"' 69 | ); 70 | test.strictSame(params.build(), [42, 'aaa']); 71 | } 72 | ); 73 | 74 | test.testSync( 75 | 'insert multiple items with returning key alias', 76 | (test, { builder, params }) => { 77 | builder 78 | .table('Table') 79 | .value('a', 42) 80 | .value('b', 'aaa') 81 | .returning('id', 'key'); 82 | 83 | test.strictSame( 84 | builder.build(), 85 | 'INSERT INTO "Table" ("a", "b") VALUES ($1, $2) RETURNING "id" AS "key"' 86 | ); 87 | test.strictSame(params.build(), [42, 'aaa']); 88 | } 89 | ); 90 | 91 | test.testSync( 92 | 'insert multiple items with returning builder alias', 93 | (test, { builder, params }) => { 94 | builder 95 | .table('Table') 96 | .value('a', 42) 97 | .value('b', 'aaa') 98 | .returning(new RawBuilder('"abc"'), 'key'); 99 | 100 | test.strictSame( 101 | builder.build(), 102 | 'INSERT INTO "Table" ("a", "b") VALUES ($1, $2) RETURNING "abc" AS "key"' 103 | ); 104 | test.strictSame(params.build(), [42, 'aaa']); 105 | } 106 | ); 107 | 108 | test.testSync( 109 | 'insert multiple items with returning all on conflict', 110 | (test, { builder, params }) => { 111 | builder 112 | .table('Table') 113 | .value('a', 42) 114 | .value('b', 'aaa') 115 | .conflict('("f1", "f2")', 'DO NOTHING') 116 | .returning('*'); 117 | 118 | test.strictSame( 119 | builder.build(), 120 | 'INSERT INTO "Table" ("a", "b") VALUES ($1, $2) ON CONFLICT ("f1", "f2") DO NOTHING RETURNING *' 121 | ); 122 | test.strictSame(params.build(), [42, 'aaa']); 123 | } 124 | ); 125 | -------------------------------------------------------------------------------- /test/pg-select-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { testSync } = require('metatests'); 4 | const { PostgresParamsBuilder, PgSelectBuilder } = require('../sql.js'); 5 | 6 | const test = testSync('PgSelectBuilder tests', null, { 7 | parallelSubtests: true, 8 | }); 9 | 10 | test.beforeEach(async () => { 11 | const params = new PostgresParamsBuilder(); 12 | return { builder: new PgSelectBuilder(params), params }; 13 | }); 14 | 15 | test.testSync('select with distinct on', (test, { builder, params }) => { 16 | builder 17 | .from('table') 18 | .select('f1', 'f2', 'f3') 19 | .distinctOn('f1') 20 | .where('f2', '=', 3) 21 | .orderBy('f1') 22 | .orderBy('f4'); 23 | const query = builder.build(); 24 | test.strictSame( 25 | query, 26 | 'SELECT DISTINCT ON ("f1") "f1", "f2", "f3" FROM "table" WHERE "f2" = $1 ORDER BY "f1" ASC, "f4" ASC' 27 | ); 28 | test.strictSame(params.build(), [3]); 29 | }); 30 | 31 | test.testSync('nested select must be PgSelectBuilder', (test, { builder }) => { 32 | const nested = builder.nested(); 33 | test.type(nested, 'PgSelectBuilder'); 34 | }); 35 | 36 | test.testSync('must provide PgSelectBuilder in select', (test, { builder }) => { 37 | test.plan(1); 38 | builder.from('table1').select((b) => { 39 | test.type(b, 'PgSelectBuilder'); 40 | return b.from('table2'); 41 | }); 42 | builder.build(); 43 | }); 44 | 45 | test.testSync('pg select with cast ::', (test, { builder, params }) => { 46 | builder 47 | .from('table') 48 | .select('f1::text') 49 | .selectAs('f2::int', 'ii') 50 | .where('f2', '=', 3) 51 | .whereILike('f3::text', 3); 52 | const query = builder.build(); 53 | test.strictSame( 54 | query, 55 | 'SELECT "f1"::text, "f2"::int AS "ii" FROM "table" WHERE "f2" = $1 AND "f3"::text ILIKE $2' 56 | ); 57 | test.strictSame(params.build(), [3, 3]); 58 | }); 59 | 60 | test.testSync('Select with clause', (test, { builder, params }) => { 61 | builder 62 | .with( 63 | 'sub', 64 | builder.nested().distinctOn('f').from('table2').whereEq('a', 42) 65 | ) 66 | .from('table') 67 | .whereEq('f1', 3) 68 | .leftJoin('sub', 'b', 'f2'); 69 | const query = builder.build(); 70 | test.strictSame( 71 | query, 72 | 'WITH "sub" AS (SELECT DISTINCT ON ("f") * FROM "table2" WHERE "a" = $1) SELECT * FROM "table" LEFT OUTER JOIN "sub" ON "b" = "f2" WHERE "f1" = $2' 73 | ); 74 | test.strictSame(params.build(), [42, 3]); 75 | }); 76 | -------------------------------------------------------------------------------- /test/sql.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { testSync } = require('metatests'); 4 | const { pg } = require('..'); 5 | 6 | testSync('Must correctly export pg utility', (test) => { 7 | const builder = pg(); 8 | builder.select('f1').from('table1').where('f2', '=', 42); 9 | test.strictEqual( 10 | builder.build(), 11 | 'SELECT "f1" FROM "table1" WHERE "f2" = $1' 12 | ); 13 | test.strictEqual(builder.buildParams(), [42]); 14 | }); 15 | -------------------------------------------------------------------------------- /test/update-builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { testSync } = require('metatests'); 4 | const { 5 | PostgresParamsBuilder, 6 | SelectBuilder, 7 | UpdateBuilder, 8 | } = require('../sql.js'); 9 | 10 | const test = testSync('UpdateBuilder tests', null, { parallelSubtests: true }); 11 | 12 | test.beforeEach(async () => { 13 | const params = new PostgresParamsBuilder(); 14 | return { builder: new UpdateBuilder(params), params }; 15 | }); 16 | 17 | test.testSync('update all items', (test, { builder, params }) => { 18 | builder.table('Table').set('a', 42).set('b', 'aaa'); 19 | 20 | test.strictSame(builder.build(), 'UPDATE "Table" SET "a" = $1, "b" = $2'); 21 | test.strictSame(params.build(), [42, 'aaa']); 22 | }); 23 | 24 | test.testSync('update all items wih alias', (test, { builder, params }) => { 25 | builder.table('Table', 't').set('a', 42).set('b', 'aaa'); 26 | 27 | test.strictSame( 28 | builder.build(), 29 | 'UPDATE "Table" AS "t" SET "a" = $1, "b" = $2' 30 | ); 31 | test.strictSame(params.build(), [42, 'aaa']); 32 | }); 33 | 34 | test.testSync('update with < condition', (test, { builder, params }) => { 35 | builder.table('Table').set('a', 'ff').where('b', '<', 42); 36 | 37 | test.strictSame( 38 | builder.build(), 39 | 'UPDATE "Table" SET "a" = $1 WHERE "b" < $2' 40 | ); 41 | test.strictSame(params.build(), ['ff', 42]); 42 | }); 43 | 44 | test.testSync( 45 | 'update with simple or condition', 46 | (test, { builder, params }) => { 47 | builder 48 | .table('Table') 49 | .whereEq('a', '42') 50 | .orWhere('b', '=', '24') 51 | .set('c', true); 52 | 53 | test.strictSame( 54 | builder.build(), 55 | 'UPDATE "Table" SET "c" = $1 WHERE "a" = $2 OR "b" = $3' 56 | ); 57 | test.strictSame(params.build(), [true, '42', '24']); 58 | } 59 | ); 60 | 61 | test.testSync('update with null and condition', (test, { builder, params }) => { 62 | builder.table('Table').set('f1', 'gg').whereMore('a', 999).whereNull('b'); 63 | 64 | test.strictSame( 65 | builder.build(), 66 | 'UPDATE "Table" SET "f1" = $1 WHERE "a" > $2 AND "b" IS NULL' 67 | ); 68 | test.strictSame(params.build(), ['gg', 999]); 69 | }); 70 | 71 | test.testSync('stringify update object', (test, { builder, params }) => { 72 | const obj = { a: 'vv', b: true }; 73 | const date = new Date(); 74 | builder.table('Table').set('a', 42).set('b', obj).set('c', date); 75 | 76 | test.strictSame( 77 | builder.build(), 78 | 'UPDATE "Table" SET "a" = $1, "b" = $2, "c" = $3' 79 | ); 80 | test.strictSame(params.build(), [42, JSON.stringify(obj), date]); 81 | }); 82 | 83 | test.testSync('update with query value', (test, { builder, params }) => { 84 | const nestedQuery = new SelectBuilder(params); 85 | nestedQuery.from('table2').select('a').where('id', '>', 42).limit(1); 86 | 87 | builder.table('Table').set('a', nestedQuery).set('b', false); 88 | 89 | test.strictSame( 90 | builder.build(), 91 | 'UPDATE "Table" SET "a" = (SELECT "a" FROM "table2" WHERE "id" > $1 LIMIT $2), "b" = $3' 92 | ); 93 | test.strictSame(params.build(), [42, 1, false]); 94 | }); 95 | 96 | test.testSync('update with nested builder', (test, { builder, params }) => { 97 | builder 98 | .table('Table') 99 | .set('a', (b) => b.from('table2').select('a').where('id', '>', 42).limit(1)) 100 | .set('b', false); 101 | 102 | test.strictSame( 103 | builder.build(), 104 | 'UPDATE "Table" SET "a" = (SELECT "a" FROM "table2" WHERE "id" > $1 LIMIT $2), "b" = $3' 105 | ); 106 | test.strictSame(params.build(), [42, 1, false]); 107 | }); 108 | 109 | test.testSync( 110 | 'update with from and conditions', 111 | (test, { builder, params }) => { 112 | builder 113 | .table('table1', 't1') 114 | .from('table2', 't2') 115 | .setKey('a', 't2.a') 116 | .setKey('b', 't2.b') 117 | .set('c', 42) 118 | .whereKey('t1.c', '=', 't2.c') 119 | .whereNotNull('t1.f'); 120 | 121 | test.strictSame( 122 | builder.build(), 123 | 'UPDATE "table1" AS "t1" SET "a" = "t2"."a", "b" = "t2"."b", "c" = $1 FROM "table2" AS "t2" WHERE "t1"."c" = "t2"."c" AND "t1"."f" IS NOT NULL' 124 | ); 125 | test.strictSame(params.build(), [42]); 126 | } 127 | ); 128 | 129 | test.testSync( 130 | 'update multiple with nested builder', 131 | (test, { builder, params }) => { 132 | builder.table('Table').sets({ 133 | a: (b) => b.from('table2').select('a').where('id', '>', 42).limit(1), 134 | b: false, 135 | }); 136 | 137 | test.strictSame( 138 | builder.build(), 139 | 'UPDATE "Table" SET "a" = (SELECT "a" FROM "table2" WHERE "id" > $1 LIMIT $2), "b" = $3' 140 | ); 141 | test.strictSame(params.build(), [42, 1, false]); 142 | } 143 | ); 144 | 145 | test.testSync('Update "with" clause', (test, { builder, params }) => { 146 | builder 147 | .with('sub', builder.select().from('table2').whereEq('a', 42)) 148 | .table('Table') 149 | .sets({ 150 | a: builder.key('sub.b'), 151 | b: false, 152 | }) 153 | .from('sub'); 154 | const query = builder.build(); 155 | test.strictSame( 156 | query, 157 | 'WITH "sub" AS (SELECT * FROM "table2" WHERE "a" = $1) UPDATE "Table" SET "a" = ("sub"."b"), "b" = $2 FROM "sub"' 158 | ); 159 | test.strictSame(params.build(), [42, false]); 160 | }); 161 | 162 | test.testSync('Update "with" clause fn', (test, { builder, params }) => { 163 | builder 164 | .with('sub', (b) => b.from('table2').whereEq('a', 42)) 165 | .table('Table') 166 | .sets({ 167 | a: builder.key('sub.b'), 168 | b: false, 169 | }) 170 | .from('sub'); 171 | const query = builder.build(); 172 | test.strictSame( 173 | query, 174 | 'WITH "sub" AS (SELECT * FROM "table2" WHERE "a" = $1) UPDATE "Table" SET "a" = ("sub"."b"), "b" = $2 FROM "sub"' 175 | ); 176 | test.strictSame(params.build(), [42, false]); 177 | }); 178 | 179 | test.testSync('Update returning', (test, { builder, params }) => { 180 | builder 181 | .table('employees') 182 | .set('sales_count', builder.raw('"sales_count" + 1')) 183 | .whereEq( 184 | 'id', 185 | builder 186 | .select() 187 | .from('accounts') 188 | .select('sales_person') 189 | .whereEq('name', 'Acme Corporation') 190 | ) 191 | .returning('*'); 192 | const query = builder.build(); 193 | test.strictSame( 194 | query, 195 | `UPDATE "employees" SET "sales_count" = ("sales_count" + 1) WHERE "id" = (SELECT "sales_person" FROM "accounts" WHERE "name" = $1) RETURNING *` 196 | ); 197 | test.strictSame(params.build(), ['Acme Corporation']); 198 | }); 199 | 200 | test.testSync('Update from select', (test, { builder, params }) => { 201 | builder 202 | .table('employees') 203 | .set('sales_count', builder.raw('"sales_count" + 1')) 204 | .set('sales_no', builder.key('d.no')) 205 | .whereEq( 206 | 'id', 207 | builder 208 | .select() 209 | .from('accounts') 210 | .select('sales_person') 211 | .whereEq('name', 'Acme Corporation') 212 | ) 213 | .fromSelect( 214 | (b) => 215 | b 216 | .select('d.no') 217 | .fromRaw(`unnest(${b.params.add([1, 2, 3])}::text[]) AS "d"("no")`), 218 | 'd' 219 | ) 220 | .returning('*'); 221 | const query = builder.build(); 222 | test.strictSame( 223 | query, 224 | `UPDATE "employees" 225 | SET "sales_count" = ("sales_count" + 1), 226 | "sales_no" = ("d"."no") 227 | FROM (SELECT "d"."no" FROM unnest($1::text[]) AS "d"("no")) AS "d" 228 | WHERE "id" = (SELECT "sales_person" FROM "accounts" WHERE "name" = $2) RETURNING *` 229 | .replace(/\n/, '') 230 | .replace(/\s+/gi, ' ') 231 | ); 232 | test.strictSame(params.build(), [[1, 2, 3], 'Acme Corporation']); 233 | }); 234 | 235 | test.testSync('Update from table', (test, { builder, params }) => { 236 | builder 237 | .table('employees') 238 | .set('sales_count', builder.raw('"sales_count" + 1')) 239 | .set('sales_no', builder.key('t2.f1')) 240 | .whereEq( 241 | 'id', 242 | builder 243 | .select() 244 | .from('accounts') 245 | .select('sales_person') 246 | .whereEq('name', 'Acme Corporation') 247 | ) 248 | .fromSelect('"table2"', 't2') 249 | .returning('*'); 250 | const query = builder.build(); 251 | test.strictSame( 252 | query, 253 | `UPDATE "employees" 254 | SET "sales_count" = ("sales_count" + 1), 255 | "sales_no" = ("t2"."f1") 256 | FROM "table2" AS "t2" 257 | WHERE "id" = (SELECT "sales_person" FROM "accounts" WHERE "name" = $1) RETURNING *` 258 | .replace(/\n/, '') 259 | .replace(/\s+/gi, ' ') 260 | ); 261 | test.strictSame(params.build(), ['Acme Corporation']); 262 | }); 263 | -------------------------------------------------------------------------------- /types/lib/conditions-builder.d.ts: -------------------------------------------------------------------------------- 1 | import { ParamsBuilder } from './params-builder'; 2 | import { 3 | QueryBuilder, 4 | QueryBuilderOptions, 5 | QueryValue, 6 | SqlTemplate, 7 | } from './query-builder'; 8 | 9 | export interface ConditionsBuilderOptions extends QueryBuilderOptions {} 10 | 11 | export type ConditionsQueryValue = 12 | | QueryBuilder 13 | | ((builder: ConditionsBuilder) => QueryBuilder); 14 | 15 | type ConditionValue = QueryValue | QueryBuilder; 16 | 17 | export class ConditionsBuilder extends QueryBuilder { 18 | constructor(params: ParamsBuilder, options?: ConditionsBuilderOptions); 19 | 20 | and(key: string, cond: string, value: ConditionValue): this; 21 | and(key: ConditionsQueryValue): this; 22 | andConds(conds: ConditionsQueryValue): this; 23 | 24 | andKey(leftKey: string, cond: string, rightKey: string): this; 25 | andRaw(sql: SqlTemplate): this; 26 | 27 | or(key: string, cond: string, value: ConditionValue): this; 28 | or(key: ConditionsQueryValue): this; 29 | orConds(conds: ConditionsQueryValue): this; 30 | 31 | orKey(leftKey: string, cond: string, rightKey: string): this; 32 | orRaw(sql: SqlTemplate): this; 33 | 34 | not(key: string, cond: string, value: ConditionValue): this; 35 | notKey(leftKey: string, cond: string, rightKey: string): this; 36 | notRaw(sql: SqlTemplate): this; 37 | 38 | orNot(key: string, cond: string, value: ConditionValue): this; 39 | orNotKey(leftKey: string, cond: string, rightKey: string): this; 40 | orNotRaw(sql: SqlTemplate): this; 41 | 42 | null(key: string): this; 43 | 44 | orNull(key: string): this; 45 | 46 | notNull(key: string): this; 47 | 48 | orNotNull(key: string): this; 49 | 50 | between( 51 | key: string, 52 | from: ConditionValue, 53 | to: ConditionValue, 54 | symmetric?: boolean 55 | ): this; 56 | 57 | orBetween( 58 | key: string, 59 | from: ConditionValue, 60 | to: ConditionValue, 61 | symmetric?: boolean 62 | ): this; 63 | 64 | notBetween( 65 | key: string, 66 | from: ConditionValue, 67 | to: ConditionValue, 68 | symmetric?: boolean 69 | ): this; 70 | 71 | orNotBetween( 72 | key: string, 73 | from: ConditionValue, 74 | to: ConditionValue, 75 | symmetric?: boolean 76 | ): this; 77 | 78 | in(key: string, conds: Iterable | QueryBuilder): this; 79 | 80 | orIn(key: string, conds: Iterable | QueryBuilder): this; 81 | 82 | notIn(key: string, conds: Iterable | QueryBuilder): this; 83 | 84 | orNotIn(key: string, conds: Iterable | QueryBuilder): this; 85 | 86 | any(key: string, value: ConditionValue): this; 87 | 88 | orAny(key: string, value: ConditionValue): this; 89 | 90 | exists(subquery: QueryBuilder): this; 91 | 92 | orExists(subquery: QueryBuilder): this; 93 | 94 | like(key: string, value: ConditionValue): this; 95 | notLike(key: string, value: ConditionValue): this; 96 | orLike(key: string, value: ConditionValue): this; 97 | orNotLike(key: string, value: ConditionValue): this; 98 | 99 | ilike(key: string, value: ConditionValue): this; 100 | notILike(key: string, value: ConditionValue): this; 101 | orILike(key: string, value: ConditionValue): this; 102 | orNotILike(key: string, value: ConditionValue): this; 103 | } 104 | -------------------------------------------------------------------------------- /types/lib/delete-builder.d.ts: -------------------------------------------------------------------------------- 1 | import { ParamsBuilder } from './params-builder'; 2 | import { QueryBuilder, QueryBuilderOptions, QueryValue } from './query-builder'; 3 | import { QueryConditionsBuilder } from './query-conditions-builder'; 4 | import { SelectConditionValue } from './select-builder'; 5 | 6 | export interface DeleteBuilderOptions extends QueryBuilderOptions {} 7 | 8 | export class DeleteBuilder extends QueryConditionsBuilder< 9 | DeleteBuilderOptions, 10 | SelectConditionValue 11 | > { 12 | constructor(params: ParamsBuilder, options?: DeleteBuilderOptions); 13 | 14 | from(tableName: string, alias?: string): this; 15 | } 16 | -------------------------------------------------------------------------------- /types/lib/insert-builder.d.ts: -------------------------------------------------------------------------------- 1 | import { ParamsBuilder } from './params-builder'; 2 | import { QueryBuilder, QueryBuilderOptions } from './query-builder'; 3 | import { 4 | SelectBuilder, 5 | SelectConditionValue, 6 | SelectQueryValue, 7 | } from './select-builder'; 8 | 9 | export interface InsertBuilderOptions extends QueryBuilderOptions {} 10 | 11 | export class InsertBuilder extends QueryBuilder { 12 | constructor(params: ParamsBuilder, options?: InsertBuilderOptions); 13 | 14 | table(tableName: string, alias?: string): this; 15 | 16 | value(key: string, value: SelectConditionValue): this; 17 | 18 | values(obj: T): this; 19 | 20 | with(alias: string | QueryBuilder, sql: SelectQueryValue): this; 21 | 22 | from(sql: SelectQueryValue): this; 23 | 24 | select(): SelectBuilder; 25 | } 26 | -------------------------------------------------------------------------------- /types/lib/params-builder.d.ts: -------------------------------------------------------------------------------- 1 | export class ParamsBuilder

{ 2 | // Add passed value to parameters 3 | // Returns a string name to put in an sql query 4 | add(value: any, options?: O): string; 5 | 6 | // Generic building method that must return the parameters object 7 | build(): P; 8 | } 9 | -------------------------------------------------------------------------------- /types/lib/pg-insert-builder.d.ts: -------------------------------------------------------------------------------- 1 | import { QueryBuilder } from './query-builder'; 2 | import { InsertBuilder } from './insert-builder'; 3 | 4 | export class PgInsertBuilder extends InsertBuilder { 5 | returning(key: string | '*' | QueryBuilder, alias?: string): this; 6 | 7 | conflict(target: string | null | undefined, action: string): this; 8 | } 9 | -------------------------------------------------------------------------------- /types/lib/pg-params-builder.d.ts: -------------------------------------------------------------------------------- 1 | import { ParamsBuilder } from './params-builder'; 2 | 3 | // Params builder for PostgreSQL database 4 | export class PostgresParamsBuilder extends ParamsBuilder> {} 5 | -------------------------------------------------------------------------------- /types/lib/pg-select-builder.d.ts: -------------------------------------------------------------------------------- 1 | import { SelectBuilder, SelectQueryValue } from './select-builder'; 2 | import { QueryBuilder, QueryValue } from './query-builder'; 3 | 4 | export type PgSelectConditionValue = 5 | | QueryBuilder 6 | | ((builder: PgSelectBuilder) => QueryBuilder) 7 | | QueryValue; 8 | 9 | export type PgSelectQueryValue = 10 | | QueryBuilder 11 | | ((builder: PgSelectBuilder) => QueryBuilder); 12 | 13 | export class PgSelectBuilder extends SelectBuilder { 14 | with(alias: string, sql: PgSelectQueryValue): this; 15 | 16 | distinctOn(keyOrExpr: string | '*' | QueryBuilder): this; 17 | } 18 | -------------------------------------------------------------------------------- /types/lib/pg.d.ts: -------------------------------------------------------------------------------- 1 | import { PostgresParamsBuilder } from './pg-params-builder'; 2 | import { SelectBuilderOptions } from './select-builder'; 3 | import { PgInsertBuilder } from './pg-insert-builder'; 4 | import { UpdateBuilder, UpdateBuilderOptions } from './update-builder'; 5 | import { DeleteBuilder, DeleteBuilderOptions } from './delete-builder'; 6 | import { PgSelectBuilder } from './pg-select-builder'; 7 | import { InsertBuilderOptions } from './insert-builder'; 8 | 9 | export const pg: typeof pgSelect; 10 | 11 | export const pgSelect: (options?: SelectBuilderOptions) => PgSelectBuilder; 12 | 13 | export const pgInsert: (options?: InsertBuilderOptions) => PgInsertBuilder; 14 | 15 | export const pgUpdate: (options?: UpdateBuilderOptions) => UpdateBuilder; 16 | 17 | export const pgDelete: (options?: DeleteBuilderOptions) => DeleteBuilder; 18 | 19 | export const pgQuerySelect: ( 20 | handler?: (builder: PgSelectBuilder, params: PostgresParamsBuilder) => void 21 | ) => Promise; 22 | 23 | export const pgQueryInsert: ( 24 | pg: { query: (query: string, params: unknown[]) => Promise }, 25 | handler: (builder: PgInsertBuilder, params: PostgresParamsBuilder) => void 26 | ) => Promise; 27 | 28 | export const pgQueryUpdate: ( 29 | pg: { query: (query: string, params: unknown[]) => Promise }, 30 | handler: (builder: UpdateBuilder, params: PostgresParamsBuilder) => void 31 | ) => Promise; 32 | 33 | export const pgQueryDelete: ( 34 | pg: { query: (query: string, params: unknown[]) => Promise }, 35 | handler: (builder: DeleteBuilder, params: PostgresParamsBuilder) => void 36 | ) => Promise; 37 | -------------------------------------------------------------------------------- /types/lib/query-builder.d.ts: -------------------------------------------------------------------------------- 1 | import { ParamsBuilder } from './params-builder'; 2 | 3 | export type QueryValue = 4 | | string 5 | | number 6 | | boolean 7 | | Date 8 | | null 9 | | QueryValue[] 10 | | Set; 11 | 12 | export interface QueryBuilderOptions { 13 | escapeIdentifier?: (key: string) => string; 14 | } 15 | 16 | // Base class for all QueryBuilders 17 | export class QueryBuilder { 18 | public params: ParamsBuilder; 19 | 20 | constructor(params: ParamsBuilder, options?: O); 21 | 22 | makeParamValue(value: unknown | QueryBuilder): string; 23 | 24 | makeKeyOrExpr(value: string | QueryBuilder): string; 25 | 26 | raw(sqlTemplate: SqlTemplate): RawBuilder; 27 | 28 | key(key: string): RawBuilder; 29 | 30 | nested(): this; 31 | 32 | // Build and return the SQL query. 33 | build(): string; 34 | 35 | buildParams(): unknown[]; 36 | } 37 | 38 | export type SqlTemplate = string | ((p: ParamsBuilder) => string); 39 | 40 | export class RawBuilder extends QueryBuilder { 41 | constructor( 42 | sqlTemplate: SqlTemplate, 43 | params: ParamsBuilder, 44 | options?: QueryBuilderOptions 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /types/lib/query-conditions-builder.d.ts: -------------------------------------------------------------------------------- 1 | import { ParamsBuilder } from './params-builder'; 2 | import { SelectQueryValue } from './select-builder'; 3 | import { 4 | QueryBuilder, 5 | QueryBuilderOptions, 6 | SqlTemplate, 7 | } from './query-builder'; 8 | import { ConditionsQueryValue } from './conditions-builder'; 9 | 10 | // Utility class for all proxy Conditions methods. 11 | export class QueryConditionsBuilder< 12 | O extends QueryBuilderOptions = QueryBuilderOptions, 13 | CV = unknown, 14 | > extends QueryBuilder { 15 | constructor(params: ParamsBuilder, options?: O); 16 | 17 | where(key: string, cond: string, value: CV): this; 18 | where(cond: ConditionsQueryValue): this; 19 | 20 | whereKey(leftKey: string, cond: string, rightKey: string): this; 21 | 22 | whereRaw(sql: SqlTemplate): this; 23 | 24 | whereEq(key: string, value: CV): this; 25 | 26 | orWhereEq(key: string, value: CV): this; 27 | 28 | whereMore(key: string, value: CV): this; 29 | 30 | orWhereMore(key: string, value: CV): this; 31 | 32 | whereMoreEq(key: string, value: CV): this; 33 | 34 | orWhereMoreEq(key: string, value: CV): this; 35 | 36 | whereLess(key: string, value: CV): this; 37 | 38 | orWhereMoreEq(key: string, value: CV): this; 39 | 40 | whereLessEq(key: string, value: CV): this; 41 | 42 | orWhereLessEq(key: string, value: CV): this; 43 | 44 | orWhere(key: string, cond: string, value: CV): this; 45 | orWhere(cond: ConditionsQueryValue): this; 46 | 47 | orWhereKey(leftKey: string, cond: string, rightKey: string): this; 48 | 49 | orWhereRaw(sql: SqlTemplate): this; 50 | 51 | whereNot(key: string, cond: string, value: CV): this; 52 | 53 | whereNotKey(leftKey: string, cond: string, rightKey: string): this; 54 | 55 | whereNotRaw(sql: SqlTemplate): this; 56 | 57 | orWhereNot(key: string, cond: string, value: CV): this; 58 | 59 | orWhereNotKey(leftKey: string, cond: string, rightKey: string): this; 60 | 61 | orWhereNotRaw(sql: SqlTemplate): this; 62 | 63 | whereNull(key: string): this; 64 | 65 | orWhereNull(key: string): this; 66 | 67 | whereNotNull(key: string): this; 68 | 69 | orWhereNotNull(key: string): this; 70 | 71 | whereBetween(key: string, from: CV, to: CV, symmetric?: boolean): this; 72 | 73 | orWhereBetween(key: string, from: CV, to: CV, symmetric?: boolean): this; 74 | 75 | whereNotBetween(key: string, from: CV, to: CV, symmetric?: boolean): this; 76 | 77 | orWhereNotBetween(key: string, from: CV, to: CV, symmetric?: boolean): this; 78 | 79 | whereIn(key: string, conds: Iterable | SelectQueryValue): this; 80 | 81 | orWhereIn(key: string, conds: Iterable | SelectQueryValue): this; 82 | 83 | whereNotIn(key: string, conds: Iterable | SelectQueryValue): this; 84 | 85 | orWhereNotIn(key: string, conds: Iterable | SelectQueryValue): this; 86 | 87 | whereAny(key: string, value: CV): this; 88 | 89 | orWhereAny(key: string, value: CV): this; 90 | 91 | whereExists(subquery: SelectQueryValue): this; 92 | 93 | orWhereExists(subquery: SelectQueryValue): this; 94 | 95 | whereLike(key: string, value: CV): this; 96 | whereNotLike(key: string, value: CV): this; 97 | orWhereLike(key: string, value: CV): this; 98 | orWhereNotLike(key: string, value: CV): this; 99 | 100 | whereILike(key: string, value: CV): this; 101 | whereNotILike(key: string, value: CV): this; 102 | orWhereILike(key: string, value: CV): this; 103 | orWhereNotILike(key: string, value: CV): this; 104 | 105 | // Build and return the SQL query. 106 | build(): string; 107 | } 108 | -------------------------------------------------------------------------------- /types/lib/select-builder.d.ts: -------------------------------------------------------------------------------- 1 | import { ParamsBuilder } from './params-builder'; 2 | import { QueryBuilder, QueryBuilderOptions, QueryValue } from './query-builder'; 3 | import { QueryConditionsBuilder } from './query-conditions-builder'; 4 | 5 | export interface SelectBuilderOptions extends QueryBuilderOptions {} 6 | 7 | export type SelectQueryValue = 8 | | QueryBuilder 9 | | ((builder: SelectBuilder) => QueryBuilder); 10 | 11 | export type SelectConditionValue = QueryValue | SelectQueryValue; 12 | 13 | export type JoinKind = 14 | | 'INNER' 15 | | 'LEFT OUTER' 16 | | 'RIGHT OUTER' 17 | | 'FULL OUTER' 18 | | 'NATURAL' 19 | | 'CROSS'; 20 | 21 | export class SelectBuilder< 22 | CV = SelectConditionValue, 23 | > extends QueryConditionsBuilder { 24 | constructor(params: ParamsBuilder, options?: SelectBuilderOptions); 25 | 26 | from(tableName: string, alias?: string): this; 27 | 28 | fromRaw(sql: string | QueryBuilder): this; 29 | 30 | with(alias: string | QueryBuilder, sql: SelectQueryValue): this; 31 | 32 | select(...fields: Array): this; 33 | 34 | selectAs(field: string | CV, alias: string): this; 35 | 36 | selectFn(fn: string, field: string | CV, alias: string): this; 37 | 38 | selectRaw(sql: string | CV): this; 39 | 40 | innerJoin(tableName: string, leftKey: string, rightKey: string): this; 41 | 42 | innerJoinAs( 43 | tableName: string, 44 | alias: string, 45 | leftKey: string, 46 | rightKey: string 47 | ): this; 48 | 49 | innerJoinCond(tableName: string, condition: QueryBuilder | string): this; 50 | 51 | innerJoinCondAs( 52 | tableName: string, 53 | alias: string, 54 | condition: QueryBuilder | string 55 | ): this; 56 | 57 | leftJoin(tableName: string, leftKey: string, rightKey: string): this; 58 | 59 | leftJoinAs( 60 | tableName: string, 61 | alias: string, 62 | leftKey: string, 63 | rightKey: string 64 | ): this; 65 | 66 | leftJoinCond(tableName: string, condition: QueryBuilder | string): this; 67 | 68 | leftJoinCondAs( 69 | tableName: string, 70 | alias: string, 71 | condition: QueryBuilder | string 72 | ): this; 73 | 74 | rightJoin(tableName: string, leftKey: string, rightKey: string): this; 75 | 76 | rightJoinAs( 77 | tableName: string, 78 | alias: string, 79 | leftKey: string, 80 | rightKey: string 81 | ): this; 82 | 83 | rightJoinCond(tableName: string, condition: QueryBuilder | string): this; 84 | 85 | rightJoinCondAs( 86 | tableName: string, 87 | alias: string, 88 | condition: QueryBuilder | string 89 | ): this; 90 | 91 | fullJoin(tableName: string, leftKey: string, rightKey: string): this; 92 | 93 | fullJoinAs( 94 | tableName: string, 95 | alias: string, 96 | leftKey: string, 97 | rightKey: string 98 | ): this; 99 | 100 | fullJoinCond(tableName: string, condition: QueryBuilder | string): this; 101 | 102 | fullJoinCondAs( 103 | tableName: string, 104 | alias: string, 105 | condition: QueryBuilder | string 106 | ): this; 107 | 108 | naturalJoin(tableName: string): this; 109 | 110 | naturalJoinAs(tableName: string, alias: string): this; 111 | 112 | crossJoin(tableName: string): this; 113 | 114 | crossJoinAs(tableName: string, alias: string): this; 115 | 116 | join( 117 | kind: JoinKind, 118 | tableName: string, 119 | alias: string, 120 | leftKey: string, 121 | rightKey: string 122 | ): this; 123 | 124 | joinCond( 125 | kind: JoinKind, 126 | tableName: string, 127 | alias: string, 128 | condition: QueryBuilder | string 129 | ): this; 130 | 131 | distinct(): this; 132 | 133 | orderBy(field: string, dir?: 'ASC' | 'DESC'): this; 134 | 135 | orderByRaw(raw: QueryBuilder | string): this; 136 | 137 | groupBy(...field: string[]): this; 138 | 139 | groupByRaw(raw: QueryBuilder | string): this; 140 | 141 | limit(limit: number): this; 142 | 143 | offset(offset: number): this; 144 | 145 | count(field?: string, alias?: string): this; 146 | 147 | countOver(field?: string, alias?: string): this; 148 | 149 | avg(field: string, alias?: string): this; 150 | 151 | min(field: string, alias?: string): this; 152 | 153 | max(field: string, alias?: string): this; 154 | 155 | sum(field: string, alias?: string): this; 156 | } 157 | -------------------------------------------------------------------------------- /types/lib/update-builder.d.ts: -------------------------------------------------------------------------------- 1 | import { ParamsBuilder } from './params-builder'; 2 | import { QueryBuilder, QueryBuilderOptions } from './query-builder'; 3 | import { QueryConditionsBuilder } from './query-conditions-builder'; 4 | import { 5 | SelectBuilder, 6 | SelectConditionValue, 7 | SelectQueryValue, 8 | } from './select-builder'; 9 | 10 | export interface UpdateBuilderOptions extends QueryBuilderOptions {} 11 | 12 | export class UpdateBuilder extends QueryConditionsBuilder< 13 | UpdateBuilderOptions, 14 | SelectConditionValue 15 | > { 16 | constructor(params: ParamsBuilder, options?: UpdateBuilderOptions); 17 | 18 | table(tableName: string, alias?: string): this; 19 | 20 | from(tableName: string, alias?: string): this; 21 | 22 | fromSelect(sql: SelectConditionValue, alias?: string): this; 23 | 24 | set(key: string, value: SelectConditionValue): this; 25 | 26 | sets(obj: T): this; 27 | 28 | with(alias: string | QueryBuilder, sql: SelectQueryValue): this; 29 | 30 | select(): SelectBuilder; 31 | 32 | returning(key: string | '*' | QueryBuilder, alias?: string): this; 33 | 34 | isNotEmpty(): boolean; 35 | } 36 | -------------------------------------------------------------------------------- /types/lib/utils.d.ts: -------------------------------------------------------------------------------- 1 | export function escapeIdentifier(name: string): string; 2 | 3 | export function pgEscapeIdentifier(name: string): string; 4 | 5 | export function escapeKey( 6 | key: string, 7 | escapeIdentifier: (id: string) => string 8 | ): string; 9 | 10 | export function mapJoinIterable( 11 | val: Iterable, 12 | mapper: (val: T) => string, 13 | sep: string 14 | ): string; 15 | 16 | export function joinIterable(val: Iterable, sep: string): string; 17 | -------------------------------------------------------------------------------- /types/sql.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/query-builder'; 2 | export * from './lib/query-conditions-builder'; 3 | export { SelectBuilder, SelectBuilderOptions } from './lib/select-builder'; 4 | export { 5 | ConditionsBuilder, 6 | ConditionsBuilderOptions, 7 | } from './lib/conditions-builder'; 8 | export * from './lib/params-builder'; 9 | export * from './lib/pg-params-builder'; 10 | export * from './lib/insert-builder'; 11 | export * from './lib/delete-builder'; 12 | export * from './lib/update-builder'; 13 | export * from './lib/pg-insert-builder'; 14 | export * from './lib/pg-select-builder'; 15 | export * from './lib/pg'; 16 | export * from './lib/utils'; 17 | -------------------------------------------------------------------------------- /types/test/conditions-builder.ts: -------------------------------------------------------------------------------- 1 | import { ParamsBuilder } from '../lib/params-builder'; 2 | import { QueryBuilder } from '../lib/query-builder'; 3 | import { ConditionsBuilder } from '../lib/conditions-builder'; 4 | 5 | const anotherQuery: QueryBuilder = {} as QueryBuilder; 6 | 7 | const queryBuilderFn = (select: ConditionsBuilder): QueryBuilder => select; 8 | 9 | const params = new ParamsBuilder(); 10 | const builder: ConditionsBuilder = new ConditionsBuilder(params); 11 | 12 | const sql = builder 13 | .and('key', '>', 42) 14 | .or('key', '>', 42) 15 | .and('key', '>', anotherQuery) 16 | .or('key', '>', anotherQuery) 17 | .andConds(anotherQuery) 18 | .orConds(anotherQuery) 19 | .andConds(queryBuilderFn) 20 | .orConds(queryBuilderFn) 21 | .not('keynot', '<', 42) 22 | .orNot('keynot', '<', 42) 23 | .not('keynot', '<', anotherQuery) 24 | .orNot('keynot', '<', anotherQuery) 25 | .null('keynull') 26 | .orNull('keynull') 27 | .notNull('keynotnull') 28 | .orNotNull('keynotnull') 29 | .between('keybetween', 1, 100) 30 | .orBetween('keybetween', 1, 100) 31 | .between('keybetween', 1, 100, true) 32 | .orBetween('keybetween', 1, 100, true) 33 | .notBetween('keynotbetween', 1, 100) 34 | .orNotBetween('keynotbetween', 1, 100) 35 | .notBetween('keynotbetween', 1, 100, true) 36 | .orNotBetween('keynotbetween', 1, 100, true) 37 | .between('keybetween', anotherQuery, anotherQuery) 38 | .orBetween('keybetween', anotherQuery, anotherQuery) 39 | .between('keybetween', anotherQuery, anotherQuery, true) 40 | .orBetween('keybetween', anotherQuery, anotherQuery, true) 41 | .notBetween('keynotbetween', anotherQuery, anotherQuery) 42 | .orNotBetween('keynotbetween', anotherQuery, anotherQuery) 43 | .notBetween('keynotbetween', anotherQuery, anotherQuery, true) 44 | .orNotBetween('keynotbetween', anotherQuery, anotherQuery, true) 45 | .in('keyin', [1, 2, 3]) 46 | .orIn('keyin', [1, 2, 3]) 47 | .in('keyin', new Set([1, 2, 3])) 48 | .orIn('keyin', new Set([1, 2, 3])) 49 | .in('keyin', anotherQuery) 50 | .orIn('keyin', anotherQuery) 51 | .notIn('keynotin', [1, 2, 3]) 52 | .orNotIn('keynotin', [1, 2, 3]) 53 | .notIn('keynotin', new Set([1, 2, 3])) 54 | .orNotIn('keynotin', new Set([1, 2, 3])) 55 | .notIn('keynotin', anotherQuery) 56 | .orNotIn('keynotin', anotherQuery) 57 | .any('keyany', [1, 2, 3]) 58 | .orAny('keyany', [1, 2, 3]) 59 | .any('keyany', new Set([1, 2, 3])) 60 | .orAny('keyany', new Set([1, 2, 3])) 61 | .any('keyany', anotherQuery) 62 | .orAny('keyany', anotherQuery) 63 | .exists(anotherQuery) 64 | .orExists(anotherQuery) 65 | .build(); 66 | 67 | const p: any[] = params.build(); 68 | -------------------------------------------------------------------------------- /types/test/select-builder.ts: -------------------------------------------------------------------------------- 1 | import { ParamsBuilder } from '../lib/params-builder'; 2 | import { QueryBuilder } from '../lib/query-builder'; 3 | import { SelectBuilder } from '../lib/select-builder'; 4 | 5 | const anotherQuery: QueryBuilder = {} as QueryBuilder; 6 | 7 | const queryBuilderFn = (select: SelectBuilder): QueryBuilder => select; 8 | 9 | const params = new ParamsBuilder(); 10 | const builder: SelectBuilder = new SelectBuilder(params); 11 | const sql = builder 12 | .from('table1') 13 | .select('a', 'b') 14 | .selectAs('c', 'alias') 15 | .selectRaw(builder.nested().from('table2').limit(1)) 16 | .select((b) => b.nested().from('table2').limit(1)) 17 | .selectAs((b) => b.nested().from('table2').limit(1), 'f2') 18 | .innerJoin('table2', 'key1', 'key2') 19 | .distinct() 20 | .where('key', '>', 42) 21 | .whereEq('key', 42) 22 | .whereMore('key', 42) 23 | .whereMoreEq('key', 42) 24 | .whereLess('key', 42) 25 | .whereLessEq('key', 42) 26 | .orWhere('key', '>', 42) 27 | .where('key', '>', anotherQuery) 28 | .orWhere('key', '>', anotherQuery) 29 | .where('key', '>', queryBuilderFn) 30 | .orWhere('key', '>', queryBuilderFn) 31 | .whereNot('keynot', '<', 42) 32 | .orWhereNot('keynot', '<', 42) 33 | .whereNot('keynot', '<', anotherQuery) 34 | .orWhereNot('keynot', '<', anotherQuery) 35 | .whereNot('keynot', '<', queryBuilderFn) 36 | .orWhereNot('keynot', '<', queryBuilderFn) 37 | .whereNull('keynull') 38 | .orWhereNull('keynull') 39 | .whereNotNull('keynotnull') 40 | .orWhereNotNull('keynotnull') 41 | .whereBetween('keybetween', 1, 100) 42 | .orWhereBetween('keybetween', 1, 100) 43 | .whereBetween('keybetween', 1, 100, true) 44 | .orWhereBetween('keybetween', 1, 100, true) 45 | .whereNotBetween('keynotbetween', 1, 100) 46 | .orWhereNotBetween('keynotbetween', 1, 100) 47 | .whereNotBetween('keynotbetween', 1, 100, true) 48 | .orWhereNotBetween('keynotbetween', 1, 100, true) 49 | .whereBetween('keybetween', anotherQuery, anotherQuery) 50 | .orWhereBetween('keybetween', anotherQuery, anotherQuery) 51 | .whereBetween('keybetween', anotherQuery, anotherQuery, true) 52 | .orWhereBetween('keybetween', anotherQuery, anotherQuery, true) 53 | .whereNotBetween('keynotbetween', anotherQuery, anotherQuery) 54 | .orWhereNotBetween('keynotbetween', anotherQuery, anotherQuery) 55 | .whereNotBetween('keynotbetween', anotherQuery, anotherQuery, true) 56 | .orWhereNotBetween('keynotbetween', anotherQuery, anotherQuery, true) 57 | .whereBetween('keybetween', queryBuilderFn, queryBuilderFn) 58 | .orWhereBetween('keybetween', queryBuilderFn, queryBuilderFn) 59 | .whereBetween('keybetween', queryBuilderFn, queryBuilderFn, true) 60 | .orWhereBetween('keybetween', queryBuilderFn, queryBuilderFn, true) 61 | .whereNotBetween('keynotbetween', queryBuilderFn, queryBuilderFn) 62 | .orWhereNotBetween('keynotbetween', queryBuilderFn, queryBuilderFn) 63 | .whereNotBetween('keynotbetween', queryBuilderFn, queryBuilderFn, true) 64 | .orWhereNotBetween('keynotbetween', queryBuilderFn, queryBuilderFn, true) 65 | .whereIn('keyin', [1, 2, 3]) 66 | .orWhereIn('keyin', [1, 2, 3]) 67 | .whereIn('keyin', new Set([1, 2, 3])) 68 | .orWhereIn('keyin', new Set([1, 2, 3])) 69 | .whereIn('keyin', anotherQuery) 70 | .orWhereIn('keyin', anotherQuery) 71 | .whereIn('keyin', queryBuilderFn) 72 | .orWhereIn('keyin', queryBuilderFn) 73 | .whereNotIn('keynotin', [1, 2, 3]) 74 | .orWhereNotIn('keynotin', [1, 2, 3]) 75 | .whereNotIn('keynotin', new Set([1, 2, 3])) 76 | .orWhereNotIn('keynotin', new Set([1, 2, 3])) 77 | .whereNotIn('keynotin', anotherQuery) 78 | .orWhereNotIn('keynotin', anotherQuery) 79 | .whereNotIn('keynotin', queryBuilderFn) 80 | .orWhereNotIn('keynotin', queryBuilderFn) 81 | .whereAny('keyany', [1, 2, 3]) 82 | .orWhereAny('keyany', [1, 2, 3]) 83 | .whereAny('keyany', new Set([1, 2, 3])) 84 | .orWhereAny('keyany', new Set([1, 2, 3])) 85 | .whereAny('keyany', anotherQuery) 86 | .orWhereAny('keyany', anotherQuery) 87 | .whereAny('keyany', queryBuilderFn) 88 | .orWhereAny('keyany', queryBuilderFn) 89 | .whereExists(anotherQuery) 90 | .orWhereExists(anotherQuery) 91 | .whereExists(queryBuilderFn) 92 | .orWhereExists(queryBuilderFn) 93 | .orderBy('a') 94 | .orderBy('a', 'ASC') 95 | .orderBy('a', 'DESC') 96 | .groupBy('x', 'z') 97 | .limit(42) 98 | .offset(13) 99 | .count() 100 | .count('*') 101 | .count('a', '42') 102 | .avg('f1') 103 | .avg('f2', 'a2') 104 | .min('f3') 105 | .min('f4', 'a4') 106 | .max('f5') 107 | .max('f5', 'a5') 108 | .sum('f6') 109 | .sum('f6', 'a6') 110 | .build(); 111 | 112 | const p: any[] = params.build(); 113 | -------------------------------------------------------------------------------- /types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "moduleResolution": "node", 5 | "strict": true, 6 | "noEmit": true, 7 | "baseUrl": ".", 8 | "preserveWatchOutput": true 9 | }, 10 | "include": ["*.ts", "lib/**/*.ts", "test/**/*.ts"] 11 | } 12 | --------------------------------------------------------------------------------