├── .DS_Store ├── .eslintrc.json ├── .github ├── semantic.yml └── workflows │ └── nodejs.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── .vscode └── settings.json ├── @types └── sliced │ └── index.d.ts ├── LICENSE ├── README.md ├── lib ├── column.ts ├── configTypes.ts ├── dialect │ ├── dialect.ts │ ├── mapper.ts │ ├── mssql.ts │ ├── mysql.ts │ ├── oracle.ts │ ├── postgres.ts │ └── sqlite.ts ├── functions.ts ├── index.ts ├── joiner.ts ├── node │ ├── _internal.ts │ ├── addColumn.ts │ ├── alias.ts │ ├── alter.ts │ ├── arrayCall.ts │ ├── asOf.ts │ ├── at.ts │ ├── binary.ts │ ├── cascade.ts │ ├── case.ts │ ├── cast.ts │ ├── column.ts │ ├── create.ts │ ├── createIndex.ts │ ├── createView.ts │ ├── default.ts │ ├── delete.ts │ ├── distinct.ts │ ├── distinctOn.ts │ ├── drop.ts │ ├── dropColumn.ts │ ├── dropIndex.ts │ ├── forShare.ts │ ├── forUpdate.ts │ ├── foreignKey.ts │ ├── from.ts │ ├── functionCall.ts │ ├── groupBy.ts │ ├── having.ts │ ├── ifExists.ts │ ├── ifNotExists.ts │ ├── in.ts │ ├── indexes.ts │ ├── insert.ts │ ├── interval.ts │ ├── join.ts │ ├── literal.ts │ ├── modifier.ts │ ├── node.ts │ ├── notIn.ts │ ├── onConflict.ts │ ├── onDuplicate.ts │ ├── operator.ts │ ├── orIgnore.ts │ ├── orderBy.ts │ ├── orderByValue.ts │ ├── parameter.ts │ ├── postfixUnary.ts │ ├── prefixUnary.ts │ ├── query.ts │ ├── rename.ts │ ├── renameColumn.ts │ ├── replace.ts │ ├── restrict.ts │ ├── returning.ts │ ├── rowCall.ts │ ├── select.ts │ ├── slice.ts │ ├── table.ts │ ├── ternary.ts │ ├── text.ts │ ├── truncate.ts │ ├── update.ts │ ├── valueExpression.ts │ └── where.ts ├── nodeable.ts ├── sql.ts ├── table.ts └── tableTracker.ts ├── package-lock.json ├── package.json ├── rollup.config.js ├── test ├── .eslintrc.json ├── binary-clause-tests.ts ├── clause-definition.ts ├── column-tests.ts ├── dialects │ ├── aggregate-tests.ts │ ├── alias-tests.ts │ ├── alter-table-tests.ts │ ├── array-tests.ts │ ├── as-of-tests.ts │ ├── binary-clause-tests.ts │ ├── case-tests.ts │ ├── cast-tests.ts │ ├── clause-ordering-tests.ts │ ├── create-table-tests.ts │ ├── create-view-tests.ts │ ├── date-tests.ts │ ├── delete-tests.ts │ ├── distinct-on-tests.ts │ ├── distinct-tests.ts │ ├── drop-table-tests.ts │ ├── for-share-tests.ts │ ├── for-update-tests.ts │ ├── from-clause-tests.ts │ ├── function-tests.ts │ ├── group-by-tests.ts │ ├── having-tests.ts │ ├── hstore-tests.ts │ ├── ilike-tests.ts │ ├── in-clause-tests.ts │ ├── indexes-tests.ts │ ├── insert-tests.ts │ ├── join-tests.ts │ ├── join-to-tests.ts │ ├── json-tests.ts │ ├── limit-and-offset-tests.ts │ ├── literal-tests.ts │ ├── matches-test.ts │ ├── namespace-tests.ts │ ├── not-in-clause-tests.ts │ ├── order-tests.ts │ ├── regex-tests.ts │ ├── replace-tests.ts │ ├── returning-tests.ts │ ├── row-tests.ts │ ├── schema-tests.ts │ ├── select-tests.ts │ ├── shortcut-tests.ts │ ├── subfield-tests.ts │ ├── subquery-tests.ts │ ├── support.ts │ ├── table-tests.ts │ ├── ternary-clause-tests.ts │ ├── tostring-tests.ts │ ├── truncate-table-tests.ts │ ├── unary-clause-tests.ts │ ├── update-tests.ts │ ├── value-expression-tests.ts │ └── where-clause-tests.ts ├── function-tests.ts ├── index-tests.ts ├── operator-tests.ts ├── select-tests.ts ├── table-tests.ts ├── ternary-clause-tests.ts ├── tsconfig.json └── unary-clause-tests.ts ├── tsconfig.build.json └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charsleysa/node-sql-ts/213e00f17e30e9d17bee08e7916cd3d0d0aeb04c/.DS_Store -------------------------------------------------------------------------------- /.github/semantic.yml: -------------------------------------------------------------------------------- 1 | titleOnly: true 2 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: 'Build & Test' 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Setup node 12 | uses: actions/setup-node@v4 13 | with: 14 | node-version: '20' 15 | - name: npm install --global rollup 16 | run: npm install --global rollup 17 | env: 18 | CI: true 19 | - name: npm install 20 | run: npm install 21 | env: 22 | CI: true 23 | - name: npm run test 24 | run: npm run test 25 | env: 26 | CI: true 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | spec/ 4 | .idea 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .vscode/ 3 | @types/ 4 | lib/ 5 | node_modules/ 6 | spec/ 7 | test/ 8 | .eslintrc.json 9 | .gitignore 10 | .prettierrc 11 | .travis.yml 12 | runtests.js 13 | rollup.config.js 14 | tsconfig.build.json 15 | tsconfig.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/prettierrc", 3 | "singleQuote": true, 4 | "arrowParens": "always", 5 | "bracketSpacing": true, 6 | "tabWidth": 4, 7 | "printWidth": 140 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /@types/sliced/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'sliced' { 2 | /** 3 | * Returns a section of an array. 4 | * @param args something with a length. 5 | * @param start The beginning of the specified portion of the array. 6 | * @param end The end of the specified portion of the array. 7 | */ 8 | function sliced(args: T[], start?: number, end?: number): T[]; 9 | function sliced(args: IArguments, start?: number, end?: number): any[]; 10 | 11 | export = sliced; 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011 Brian Carlson 2 | Copyright (C) 2019 Stefan Charsley 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sql-ts 2 | 3 | _sql string builder for node_ - supports PostgreSQL, mysql, Microsoft SQL Server, Oracle and sqlite dialects. 4 | 5 | Building SQL statements by hand is no fun, especially in a language which has clumsy support for multi-line strings. 6 | 7 | So let's build it with TypeScript (JavaScript also supported). 8 | 9 | Maybe it's still not fun, but at least it's _less not fun_. 10 | 11 | ![CI/CD](https://github.com/charsleysa/node-sql-ts/workflows/CI/CD/badge.svg) 12 | 13 | ## install 14 | 15 | ```sh 16 | $ npm install sql-ts 17 | ``` 18 | 19 | ## use 20 | 21 | ```ts 22 | //require the module 23 | import { Sql } from 'sql-ts'; 24 | 25 | //(optionally) set the SQL dialect 26 | const sql = new Sql('postgres'); 27 | //possible dialects: mssql, mysql, postgres (default), sqlite 28 | 29 | //first we define our tables 30 | const user = sql.define<{ id: number; name: string; email: string; lastLogin: Date }>({ 31 | name: 'user', 32 | columns: ['id', 'name', 'email', 'lastLogin'] 33 | }); 34 | 35 | const post = sql.define<{ id: number; userId: number; date: Date; title: string; body: string }>({ 36 | name: 'post', 37 | columns: ['id', 'userId', 'date', 'title', 'body'] 38 | }); 39 | 40 | //now let's make a simple query 41 | const query = user 42 | .select(user.star()) 43 | .from(user) 44 | .toQuery(); 45 | console.log(query.text); //SELECT "user".* FROM "user" 46 | 47 | //something more interesting 48 | const query = user 49 | .select(user.id) 50 | .from(user) 51 | .where(user.name.equals('boom').and(user.id.equals(1))) 52 | .or(user.name.equals('bang').and(user.id.equals(2))) 53 | .toQuery(); 54 | 55 | //query is parameterized by default 56 | console.log(query.text); //SELECT "user"."id" FROM "user" WHERE ((("user"."name" = $1) AND ("user"."id" = $2)) OR (("user"."name" = $3) AND ("user"."id" = $4))) 57 | 58 | console.log(query.values); //['boom', 1, 'bang', 2] 59 | 60 | //queries can be named 61 | const query = user 62 | .select(user.star()) 63 | .from(user) 64 | .toNamedQuery('user.all'); 65 | console.log(query.name); //'user.all' 66 | 67 | //how about a join? 68 | const query = user 69 | .select(user.name, post.body) 70 | .from(user.join(post).on(user.id.equals(post.userId))) 71 | .toQuery(); 72 | 73 | console.log(query.text); //'SELECT "user"."name", "post"."body" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId")' 74 | 75 | //this also makes parts of your queries composable, which is handy 76 | 77 | const friendship = sql.define<{ userId: number; friendId: number }>({ 78 | name: 'friendship', 79 | columns: ['userId', 'friendId'] 80 | }); 81 | 82 | const friends = user.as('friends'); 83 | const userToFriends = user 84 | .leftJoin(friendship) 85 | .on(user.id.equals(friendship.userId)) 86 | .leftJoin(friends) 87 | .on(friendship.friendId.equals(friends.id)); 88 | 89 | //and now...compose... 90 | const friendsWhoHaveLoggedInQuery = user.from(userToFriends).where(friends.lastLogin.isNotNull()); 91 | //SELECT * FROM "user" 92 | //LEFT JOIN "friendship" ON ("user"."id" = "friendship"."userId") 93 | //LEFT JOIN "user" AS "friends" ON ("friendship"."friendId" = "friends"."id") 94 | //WHERE "friends"."lastLogin" IS NOT NULL 95 | 96 | const friendsWhoUseGmailQuery = user.from(userToFriends).where(friends.email.like('%@gmail.com')); 97 | //SELECT * FROM "user" 98 | //LEFT JOIN "friendship" ON ("user"."id" = "friendship"."userId") 99 | //LEFT JOIN "user" AS "friends" ON ("friendship"."friendId" = "friends"."id") 100 | //WHERE "friends"."email" LIKE %1 101 | 102 | //Using different property names for columns 103 | //helpful if your column name is long or not camelCase 104 | const user = sql.define<{ id: number; state: string }>({ 105 | name: 'user', 106 | columns: [ 107 | { 108 | name: 'id' 109 | }, 110 | { 111 | name: 'state_or_province', 112 | property: 'state' 113 | } 114 | ] 115 | }); 116 | 117 | //now, instead of user.state_or_province, you can just use user.state 118 | console.log( 119 | user 120 | .select() 121 | .where(user.state.equals('WA')) 122 | .toQuery().text 123 | ); 124 | // "SELECT "user".* FROM "user" WHERE ("user"."state_or_province" = $1)" 125 | ``` 126 | 127 | There are a **lot** more examples included in the [test/dialects](https://github.com/charsleysa/node-sql-ts/tree/master/test/dialects) folder. We encourage you to read through them if you have any questions on usage! 128 | 129 | ## contributing 130 | 131 | We **love** contributions. 132 | 133 | node-sql-ts wouldn't be anything without all the contributors and collaborators who've worked on it. 134 | If you'd like to become a collaborator here's how it's done: 135 | 136 | 1. fork the repo 137 | 2. `git pull https://github.com/(your_username)/node-sql-ts` 138 | 3. `cd node-sql-ts` 139 | 4. `npm install --global rollup` 140 | 5. `npm install` 141 | 6. `npm test` 142 | 143 | At this point the tests should pass for you. If they don't pass please open an issue with the output or you can even send me an email directly. 144 | My email address is on my github profile and also on every commit I contributed in the repo. 145 | 146 | Once the tests are passing, modify as you see fit. _Please_ make sure you write tests to cover your modifications. Once you're ready, commit your changes and submit a pull request. 147 | 148 | **As long as your pull request doesn't have completely off-the-wall changes and it does have tests we will almost always merge it and push it to npm** 149 | 150 | If you think your changes are too off-the-wall, open an issue or a pull-request without code so we can discuss them before you begin. 151 | 152 | Usually after a few high-quality pull requests and friendly interactions we will gladly share collaboration rights with you. 153 | 154 | After all, open source belongs to everyone. 155 | 156 | ## license 157 | 158 | MIT 159 | -------------------------------------------------------------------------------- /lib/column.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | import { ColumnNode } from './node/column.js'; 3 | import { Node } from './node/node.js'; 4 | import { OrderByValueNode } from './node/orderByValue.js'; 5 | import { ValueExpressionMixin } from './node/_internal.js'; 6 | import { TextNode } from './node/text.js'; 7 | import { INodeable } from './nodeable.js'; 8 | import { Table } from './table.js'; 9 | 10 | interface ColumnConfig { 11 | name?: string; 12 | property?: string; 13 | autoGenerated?: boolean; 14 | jsType?: any; 15 | dataType?: string; 16 | primaryKey?: boolean; 17 | references?: 18 | | string 19 | | { 20 | table?: string; 21 | column?: string; 22 | constraint?: string; 23 | onDelete?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; 24 | onUpdate?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; 25 | }; 26 | notNull?: boolean; 27 | unique?: boolean; 28 | defaultValue?: any; 29 | subfields?: string[]; 30 | table?: Table; 31 | star?: boolean; 32 | subfieldContainer?: Column; 33 | isConstant?: boolean; 34 | constantValue?: any; 35 | } 36 | 37 | abstract class ColumnBase implements INodeable { 38 | public abstract toNode(): Node; 39 | } 40 | 41 | export class Column extends ValueExpressionMixin(ColumnBase) implements INodeable { 42 | public name: string; 43 | public property?: string; 44 | public table?: Table; 45 | public alias?: string; 46 | public dataType?: string; 47 | public _value: any; 48 | public star?: boolean; 49 | public subfields: { [key: string]: Column } = {}; 50 | public asArray?: boolean = false; 51 | public aggregator?: string; 52 | public isConstant?: boolean; 53 | public constantValue?: any; 54 | public primaryKey?: boolean; 55 | public notNull?: boolean; 56 | public defaultValue?: any; 57 | public references?: 58 | | string 59 | | { 60 | table?: string; 61 | column?: string; 62 | constraint?: string; 63 | onDelete?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; 64 | onUpdate?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; 65 | }; 66 | public subfieldContainer?: Column; 67 | public autoGenerated?: boolean; 68 | public unique?: boolean; 69 | public isDistinct?: boolean; 70 | constructor(config: ColumnConfig) { 71 | super(); 72 | this.name = config.name as string; 73 | this.property = config.property; 74 | this.star = config.star; 75 | this.table = config.table; 76 | this.alias = undefined; 77 | this.isConstant = config.isConstant; 78 | this.constantValue = config.constantValue; 79 | this.dataType = config.dataType; 80 | this.primaryKey = config.primaryKey; 81 | this.notNull = config.notNull; 82 | this.defaultValue = config.defaultValue; 83 | this.references = config.references; 84 | this.subfieldContainer = config.subfieldContainer; 85 | this.autoGenerated = config.autoGenerated; 86 | this.unique = config.unique; 87 | } 88 | public value(value: any): Column { 89 | const context = contextify(this); 90 | context._value = value; 91 | return context; 92 | } 93 | public getValue() { 94 | return this._value; 95 | } 96 | public toNode(): ColumnNode { 97 | // creates a query node from this column 98 | return new ColumnNode(contextify(this)); 99 | } 100 | public as(alias: string): ColumnNode { 101 | const context = contextify(this); 102 | context.alias = alias; 103 | return new ColumnNode(context); 104 | } 105 | public asc(): OrderByValueNode { 106 | return new OrderByValueNode({ 107 | value: this.toNode() 108 | }) 109 | } 110 | public desc(): OrderByValueNode { 111 | return new OrderByValueNode({ 112 | direction: new TextNode('DESC'), 113 | value: this.toNode() 114 | }) 115 | } 116 | public arrayAgg(alias?: string): ColumnNode { 117 | const context = contextify(this); 118 | context.asArray = true; 119 | context.alias = alias || context.name + 's'; 120 | return new ColumnNode(context); 121 | } 122 | public aggregate(alias: string | undefined, aggregator: string): ColumnNode { 123 | const context = contextify(this); 124 | context.aggregator = aggregator.toUpperCase(); 125 | context.alias = alias || context.name + '_' + context.aggregator.toLowerCase(); 126 | return new ColumnNode(context); 127 | } 128 | public count(alias?: string) { 129 | return this.aggregate(alias, 'count'); 130 | } 131 | public min(alias?: string) { 132 | return this.aggregate(alias, 'min'); 133 | } 134 | public max(alias?: string) { 135 | return this.aggregate(alias, 'max'); 136 | } 137 | public sum(alias?: string) { 138 | return this.aggregate(alias, 'sum'); 139 | } 140 | public avg(alias?: string) { 141 | return this.aggregate(alias, 'avg'); 142 | } 143 | public distinct(): ColumnNode { 144 | const context = contextify(this); 145 | context.isDistinct = true; 146 | return new ColumnNode(context); 147 | } 148 | public toQuery() { 149 | return this.toNode().toQuery(); 150 | } 151 | } 152 | 153 | const contextify = (base: Column): Column => { 154 | const context = Object.create(Column.prototype); 155 | Object.keys(base).forEach((key) => { 156 | context[key] = base[key as keyof Column]; 157 | }); 158 | return context; 159 | }; 160 | -------------------------------------------------------------------------------- /lib/configTypes.ts: -------------------------------------------------------------------------------- 1 | import { Sql } from './sql.js'; 2 | 3 | export type SQLDialects = 'mssql' | 'mysql' | 'oracle' | 'postgres' | 'sqlite'; 4 | 5 | export interface ColumnDefinitionBase { 6 | property?: string; 7 | autoGenerated?: boolean; 8 | dataType?: string; 9 | primaryKey?: boolean; 10 | references?: 11 | | string 12 | | { 13 | table?: string; 14 | column?: string; 15 | constraint?: string; 16 | onDelete?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; 17 | onUpdate?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; 18 | }; 19 | notNull?: boolean; 20 | unique?: boolean; 21 | defaultValue?: any; 22 | subfields?: string[]; 23 | } 24 | 25 | export interface ColumnDefinition extends ColumnDefinitionBase { 26 | name: string; 27 | } 28 | 29 | export interface ColumnDefinitionObject { 30 | [name: string]: ColumnDefinitionBase; 31 | } 32 | 33 | export interface ForeignKeyDefinition { 34 | name?: string; 35 | table: string; 36 | columns: string[]; 37 | refColumns?: string[]; 38 | onDelete?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; 39 | onUpdate?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; 40 | } 41 | 42 | export interface TableDefinition { 43 | name: string; 44 | schema?: string; 45 | columns: (ColumnDefinition | string)[] | ColumnDefinitionObject; 46 | isTemporary?: boolean; 47 | foreignKeys?: ForeignKeyDefinition | ForeignKeyDefinition[]; 48 | sql?: Sql; 49 | engine?: string; 50 | charset?: string; 51 | snakeToCamel?: boolean; 52 | columnWhiteList?: boolean; 53 | } 54 | -------------------------------------------------------------------------------- /lib/dialect/mapper.ts: -------------------------------------------------------------------------------- 1 | import type { Dialect } from './dialect.js'; 2 | 3 | const dialects = new Map Dialect>(); 4 | 5 | export const registerDialect = (dialect: string, ctor: new (...args: any[]) => Dialect) => { 6 | dialects.set(dialect, ctor); 7 | } 8 | 9 | // given a dialect name, return the class 10 | export const getDialect = (dialect: string): new (...args: any[]) => Dialect => { 11 | const foundDialect = dialects.get(dialect.toLowerCase()); 12 | if (foundDialect == null) { 13 | throw new Error(dialect + ' is unsupported'); 14 | } else { 15 | return foundDialect; 16 | } 17 | }; 18 | 19 | // default dialect is postgres 20 | export const DEFAULT_DIALECT = 'postgres'; -------------------------------------------------------------------------------- /lib/dialect/postgres.ts: -------------------------------------------------------------------------------- 1 | import isFunction from 'lodash/isFunction.js'; 2 | import padStart from 'lodash/padStart.js'; 3 | 4 | import { OrderByNode } from '../node/orderBy.js'; 5 | import { Dialect } from './dialect.js'; 6 | 7 | const dateToStringUTC = (date: Date) => { 8 | let year = date.getUTCFullYear(); 9 | const isBCYear = year < 1; 10 | if (isBCYear) year = Math.abs(year) + 1; // negative years are 1 off their BC representation 11 | 12 | let ret = 13 | padStart(String(year), 4, '0') + 14 | '-' + 15 | padStart(String(date.getUTCMonth() + 1), 2, '0') + 16 | '-' + 17 | padStart(String(date.getUTCDate()), 2, '0') + 18 | 'T' + 19 | padStart(String(date.getUTCHours()), 2, '0') + 20 | ':' + 21 | padStart(String(date.getUTCMinutes()), 2, '0') + 22 | ':' + 23 | padStart(String(date.getUTCSeconds()), 2, '0') + 24 | '.' + 25 | padStart(String(date.getUTCMilliseconds()), 3, '0'); 26 | 27 | ret += 'Z'; 28 | if (isBCYear) ret += ' BC'; 29 | return ret; 30 | } 31 | 32 | /** 33 | * Config can contain: 34 | * 35 | * nullOrder: 'first' | 'last' 36 | * 37 | * @param config 38 | * @constructor 39 | */ 40 | export class Postgres extends Dialect<{ nullOrder?: string }> { 41 | constructor(config: { nullOrder?: string }) { 42 | super(config); 43 | } 44 | 45 | protected createSubInstance() { 46 | return new Postgres(this.config); 47 | } 48 | 49 | public _getParameterValue( 50 | value: null | boolean | number | string | any[] | Date | Buffer | Record, 51 | quoteChar?: string 52 | ): string | number { 53 | if ('object' === typeof value && Array.isArray(value)) { 54 | // naive check to see if this is an array of objects, which 55 | // is handled differently than an array of primitives 56 | if (value.length && 'object' === typeof value[0] && !isFunction(value[0].toISOString) && !Array.isArray(value[0])) { 57 | value = `'${JSON.stringify(value)}'`; 58 | } else { 59 | // In a Postgres array, strings must be double-quoted 60 | value = value.map((item) => this._getParameterValue(item, '"')); 61 | value = `'{${(value as any[]).join(',')}}'`; 62 | } 63 | } else if ('object' === typeof value && value instanceof Date) { 64 | // Date object's default toString format does not get parsed well 65 | // Handle dates using custom dateToString method for postgres 66 | value = this._getParameterValue(dateToStringUTC(value), quoteChar); 67 | } else { 68 | value = super._getParameterValue(value, quoteChar); 69 | } 70 | // value has been converted at this point 71 | return value; 72 | } 73 | public visitOrderBy(orderByNode: OrderByNode): string[] { 74 | const result = ['ORDER BY', orderByNode.nodes.map(this.visit.bind(this)).join(', ')]; 75 | if (this.config.nullOrder) { 76 | result.push('NULLS ' + this.config.nullOrder.toUpperCase()); 77 | } 78 | return result; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/functions.ts: -------------------------------------------------------------------------------- 1 | import reduce from 'lodash/reduce.js'; 2 | import sliced from 'sliced'; 3 | import { FunctionCallNode } from './node/functionCall.js'; 4 | 5 | // create a function that creates a function call of the specific name, using the specified sql instance 6 | const getFunctionCallCreator = (name: string) => { 7 | return (...args: any[]) => { 8 | // turn array-like arguments object into a true array 9 | return new FunctionCallNode(name, sliced(args)); 10 | }; 11 | }; 12 | 13 | // creates a hash of functions for a sql instance 14 | const getFunctions = (functionNames: string | string[] | readonly string[]) => { 15 | if (typeof functionNames === 'string') { return getFunctionCallCreator(functionNames); } 16 | 17 | const functions = reduce( 18 | functionNames, 19 | (reducer, name) => { 20 | (reducer as any)[name] = getFunctionCallCreator(name); 21 | return reducer; 22 | }, 23 | {} 24 | ); 25 | return functions; 26 | }; 27 | 28 | // aggregate functions available to all databases 29 | const aggregateFunctions = ['AVG', 'COUNT', 'DISTINCT', 'MAX', 'MIN', 'SUM'] as const; 30 | 31 | // common scalar functions available to most databases 32 | const scalarFunctions = [ 33 | 'ABS', 34 | 'COALESCE', 35 | 'LEFT', 36 | 'LENGTH', 37 | 'LOWER', 38 | 'LTRIM', 39 | 'RANDOM', 40 | 'RIGHT', 41 | 'ROUND', 42 | 'RTRIM', 43 | 'SUBSTR', 44 | 'TRIM', 45 | 'UPPER' 46 | ] as const; 47 | 48 | const dateFunctions = ['YEAR', 'MONTH', 'DAY', 'HOUR', 'CURRENT_TIMESTAMP'] as const; 49 | 50 | // hstore function available to Postgres 51 | const hstoreFunctions = ['HSTORE'] as const; 52 | 53 | // text search functions available to Postgres 54 | const textsearchFunctions = ['TS_RANK', 'TS_RANK_CD', 'PLAINTO_TSQUERY', 'TO_TSQUERY', 'TO_TSVECTOR', 'SETWEIGHT'] as const; 55 | 56 | // jsonb functions available to postgres 57 | const jsonbFunctions = [ 58 | 'JSONB_ARRAY_LENGTH', 59 | 'JSONB_BUILD_ARRAY', 60 | 'JSONB_BUILD_OBECT', 61 | 'JSONB_EXTRACT_PATH', 62 | 'JSONB_INSERT', 63 | 'JSONB_OBJECT', 64 | 'JSONB_PRETTY', 65 | 'JSONB_SET', 66 | 'JSONB_STRIP_NULLS', 67 | 'JSONB_TYPEOF', 68 | 'TO_JSONB', 69 | 'JSONB_ARRAY_ELEMENTS', 70 | 'JSONB_ARRAY_ELEMENTS_TEXT', 71 | 'JSONB_EACH', 72 | 'JSONB_EACH_TEXT', 73 | 'JSONB_OBJECT_KEYS', 74 | 'JSONB_AGG' 75 | ] as const; 76 | 77 | // time travel timestamp functions available to cockroachdb 78 | const timeTravelFunctions = [ 79 | 'STATEMENT_TIMESTAMP', 80 | 'FOLLOWER_READ_TIMESTAMP', 81 | 'WITH_MIN_TIMESTAMP', 82 | 'WITH_MAX_STALENESS' 83 | ] as const; 84 | 85 | const standardFunctionNames = [ 86 | ...aggregateFunctions, 87 | ...scalarFunctions, 88 | ...hstoreFunctions, 89 | ...textsearchFunctions, 90 | ...dateFunctions, 91 | ...jsonbFunctions, 92 | ...timeTravelFunctions 93 | ] as const; 94 | 95 | type StandardFunctions = { 96 | [K in (typeof standardFunctionNames)[number]]: (...args: any[]) => FunctionCallNode; 97 | } 98 | 99 | // creates a hash of standard functions for a sql instance 100 | const getStandardFunctions = (): StandardFunctions => { 101 | return getFunctions(standardFunctionNames) as StandardFunctions; 102 | }; 103 | 104 | export { StandardFunctions, getFunctions, getStandardFunctions }; 105 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './node/node.js'; 2 | import './node/_internal.js'; 3 | 4 | export * from './node/select.js'; 5 | export * from './node/insert.js'; 6 | export * from './node/replace.js'; 7 | export * from './node/update.js'; 8 | export * from './node/delete.js'; 9 | export * from './node/create.js'; 10 | export * from './node/drop.js'; 11 | export * from './node/truncate.js'; 12 | export * from './node/distinct.js'; 13 | export * from './node/distinctOn.js'; 14 | export * from './node/alias.js'; 15 | export * from './node/alter.js'; 16 | export * from './node/cast.js'; 17 | export * from './node/from.js'; 18 | export * from './node/where.js'; 19 | export * from './node/orderBy.js'; 20 | export * from './node/orderByValue.js'; 21 | export * from './node/groupBy.js'; 22 | export * from './node/having.js'; 23 | export * from './node/prefixUnary.js'; 24 | export * from './node/postfixUnary.js'; 25 | export * from './node/binary.js'; 26 | export * from './node/ternary.js'; 27 | export * from './node/in.js'; 28 | export * from './node/notIn.js'; 29 | export * from './node/case.js'; 30 | export * from './node/at.js'; 31 | export * from './node/slice.js'; 32 | export * from './node/table.js'; 33 | export * from './node/column.js'; 34 | export * from './node/foreignKey.js'; 35 | export * from './node/functionCall.js'; 36 | export * from './node/arrayCall.js'; 37 | export * from './node/rowCall.js'; 38 | export * from './node/parameter.js'; 39 | export * from './node/default.js'; 40 | export * from './node/addColumn.js'; 41 | export * from './node/dropColumn.js'; 42 | export * from './node/renameColumn.js'; 43 | export * from './node/rename.js'; 44 | export * from './node/ifExists.js'; 45 | export * from './node/ifNotExists.js'; 46 | export * from './node/orIgnore.js'; 47 | export * from './node/cascade.js'; 48 | export * from './node/restrict.js'; 49 | export * from './node/forUpdate.js'; 50 | export * from './node/forShare.js'; 51 | export * from './node/join.js'; 52 | export * from './node/literal.js'; 53 | export * from './node/returning.js'; 54 | export * from './node/onDuplicate.js'; 55 | export * from './node/onConflict.js'; 56 | export * from './node/indexes.js'; 57 | export * from './node/createIndex.js'; 58 | export * from './node/dropIndex.js'; 59 | export * from './node/createView.js'; 60 | export * from './node/interval.js'; 61 | export * from './node/modifier.js'; 62 | 63 | export * from './node/query.js'; 64 | 65 | export * from './dialect/mapper.js'; 66 | export * from './sql.js'; 67 | export * from './column.js'; 68 | export * from './table.js'; 69 | 70 | import { Dialect } from './dialect/dialect.js'; 71 | import { Mssql } from './dialect/mssql.js'; 72 | import { Mysql } from './dialect/mysql.js'; 73 | import { Oracle } from './dialect/oracle.js'; 74 | import { Postgres } from './dialect/postgres.js'; 75 | import { Sqlite } from './dialect/sqlite.js'; 76 | import { registerDialect } from './dialect/mapper.js'; 77 | 78 | registerDialect('mssql', Mssql); 79 | registerDialect('mysql', Mysql); 80 | registerDialect('oracle', Oracle); 81 | registerDialect('postgres', Postgres); 82 | registerDialect('sqlite', Sqlite); 83 | 84 | export { 85 | Dialect, 86 | Mssql, 87 | Mysql, 88 | Oracle, 89 | Postgres, 90 | Sqlite 91 | } 92 | -------------------------------------------------------------------------------- /lib/joiner.ts: -------------------------------------------------------------------------------- 1 | import { Table } from './table.js'; 2 | 3 | const getPrimaryKeyColumn = (table: Table) => { 4 | for (const col of table.columns) { 5 | if (col.primaryKey) { 6 | return col; 7 | } 8 | } 9 | return; 10 | }; 11 | 12 | const findReference = (left: Table, right: Table) => { 13 | // find reference 14 | for (const col of right.columns) { 15 | const references = col.references; 16 | if (references) { 17 | const leftName = left.getName(); 18 | if (typeof references === 'string') { 19 | if (references === leftName) { 20 | const leftCol = getPrimaryKeyColumn(left); 21 | return { 22 | left: leftCol, 23 | right: col 24 | }; 25 | } 26 | } else if (references.table === leftName) { 27 | const leftCol = references.column ? (left as any)[references.column] : getPrimaryKeyColumn(left); 28 | return { 29 | left: leftCol, 30 | right: col 31 | }; 32 | } 33 | } 34 | } 35 | return; 36 | }; 37 | // auto-join two tables based on column properties 38 | // requires one column to have { references: {table: 'foreignTableName', column: 'foreignColumnName'}} 39 | // or to have { references: 'foreignTableName'} -- in which case the foreign table's primary key is assumed 40 | const leftJoin = (left: Table, right: Table) => { 41 | let ref = findReference(left, right); 42 | if (!ref) { 43 | ref = findReference(right, left); 44 | } 45 | return left.join(right).on(ref!.left.equals(ref!.right)); 46 | }; 47 | 48 | export { leftJoin }; 49 | -------------------------------------------------------------------------------- /lib/node/_internal.ts: -------------------------------------------------------------------------------- 1 | export * from './valueExpression.js'; 2 | export * from './at.js'; 3 | export * from './binary.js'; 4 | export * from './case.js'; 5 | export * from './cast.js'; 6 | export * from './in.js'; 7 | export * from './notIn.js'; 8 | export * from './orderByValue.js'; 9 | export * from './postfixUnary.js'; 10 | export * from './prefixUnary.js'; 11 | export * from './slice.js'; 12 | export * from './ternary.js'; 13 | -------------------------------------------------------------------------------- /lib/node/addColumn.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class AddColumnNode extends Node { 4 | constructor() { 5 | super('ADD COLUMN'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/alias.ts: -------------------------------------------------------------------------------- 1 | import defaults from 'lodash/defaults.js'; 2 | import { Node } from './node.js'; 3 | 4 | export class AliasNode extends Node { 5 | public value: Node; 6 | public alias: string; 7 | constructor(value: Node, alias: string) { 8 | super('ALIAS'); 9 | 10 | this.value = value; 11 | this.alias = alias; 12 | } 13 | } 14 | 15 | export const AliasMixin = { 16 | as(this: Node, alias: string) { 17 | // create an alias node 18 | const aliasNode = new AliasNode(this, alias); 19 | 20 | // defaults the properties of the aliased node 21 | defaults(aliasNode, this); 22 | 23 | return aliasNode; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /lib/node/alter.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class AlterNode extends Node { 4 | constructor() { 5 | super('ALTER'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/arrayCall.ts: -------------------------------------------------------------------------------- 1 | import flatten from 'lodash/flatten.js'; 2 | import { AliasMixin } from './alias.js'; 3 | import { ParameterNode } from './parameter.js'; 4 | import { ValueExpressionNode } from './_internal.js'; 5 | 6 | export class ArrayCallNode extends ValueExpressionNode { 7 | constructor(args: any[]) { 8 | super('ARRAY CALL'); 9 | args = flatten(args); 10 | this.addAll(args.map(ParameterNode.getNodeOrParameterNode)); 11 | } 12 | 13 | public as = AliasMixin.as; 14 | } 15 | -------------------------------------------------------------------------------- /lib/node/asOf.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class AsOfNode extends Node { 4 | constructor() { 5 | super('AS OF'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/at.ts: -------------------------------------------------------------------------------- 1 | import { AliasMixin } from './alias.js'; 2 | import { Node } from './node.js'; 3 | import { classMap, ValueExpressionNode } from './_internal.js'; 4 | 5 | export class AtNode extends ValueExpressionNode { 6 | public value: Node; 7 | public index: Node; 8 | constructor(value: Node, index: Node) { 9 | super('AT'); 10 | this.value = value; 11 | this.index = index; 12 | } 13 | 14 | public as = AliasMixin.as; 15 | } 16 | 17 | classMap.set('AT', AtNode); 18 | -------------------------------------------------------------------------------- /lib/node/binary.ts: -------------------------------------------------------------------------------- 1 | import { AliasMixin } from './alias.js'; 2 | import { Node } from './node.js'; 3 | import { classMap, ValueExpressionNode } from './_internal.js'; 4 | 5 | export class BinaryNode extends ValueExpressionNode { 6 | public left: Node; 7 | public operator: string; 8 | public right: Node | Node[]; 9 | constructor(config: { left: Node; operator: string; right: Node | Node[] }) { 10 | super('BINARY'); 11 | this.left = config.left; 12 | this.operator = config.operator; 13 | this.right = config.right; 14 | } 15 | 16 | public as = AliasMixin.as; 17 | } 18 | 19 | classMap.set('BINARY', BinaryNode); 20 | -------------------------------------------------------------------------------- /lib/node/cascade.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class CascadeNode extends Node { 4 | constructor() { 5 | super('CASCADE'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/case.ts: -------------------------------------------------------------------------------- 1 | import { AliasMixin } from './alias.js'; 2 | import { Node } from './node.js'; 3 | import { classMap, ValueExpressionNode } from './_internal.js'; 4 | 5 | export class CaseNode extends ValueExpressionNode { 6 | public whenList: Node[]; 7 | public thenList: Node[]; 8 | public else?: Node; 9 | constructor(config: { whenList: Node[]; thenList: Node[]; else?: Node }) { 10 | super('CASE'); 11 | this.whenList = config.whenList; 12 | this.thenList = config.thenList; 13 | this.else = config.else; 14 | } 15 | 16 | public as = AliasMixin.as; 17 | } 18 | 19 | classMap.set('CASE', CaseNode); 20 | -------------------------------------------------------------------------------- /lib/node/cast.ts: -------------------------------------------------------------------------------- 1 | import { AliasMixin } from './alias.js'; 2 | import { Node } from './node.js'; 3 | import { classMap, ValueExpressionNode } from './_internal.js'; 4 | 5 | export class CastNode extends ValueExpressionNode { 6 | public value: Node; 7 | public dataType: string; 8 | constructor(value: Node, dataType: string) { 9 | super('CAST'); 10 | this.value = value; 11 | this.dataType = dataType; 12 | } 13 | 14 | public as = AliasMixin.as; 15 | } 16 | 17 | classMap.set('CAST', CastNode); 18 | -------------------------------------------------------------------------------- /lib/node/column.ts: -------------------------------------------------------------------------------- 1 | import { Column } from '../column.js'; 2 | import { Table } from '../table.js'; 3 | import { Node } from './node.js'; 4 | 5 | export class ColumnNode extends Node { 6 | public name: string; 7 | public property: string; 8 | public alias?: string; 9 | public star?: boolean; 10 | public isConstant?: boolean; 11 | public constantValue?: any; 12 | public asArray?: boolean; 13 | public aggregator?: string; 14 | public table?: Table; 15 | public value: any; 16 | public dataType?: string; 17 | public isDistinct?: boolean; 18 | public primaryKey?: boolean; 19 | public notNull?: boolean; 20 | public defaultValue?: any; 21 | public references?: 22 | | string 23 | | { 24 | table?: string; 25 | column?: string; 26 | constraint?: string; 27 | onDelete?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; 28 | onUpdate?: 'restrict' | 'cascade' | 'no action' | 'set null' | 'set default'; 29 | }; 30 | public subfieldContainer?: Column; 31 | public subfields: { [key: string]: Column }; 32 | public autoGenerated: boolean; 33 | public unique: boolean; 34 | constructor(config: Column) { 35 | super('COLUMN'); 36 | this.name = config.name; 37 | this.property = config.property || config.name; 38 | this.alias = config.alias; 39 | this.star = config.star; 40 | this.isConstant = config.isConstant; 41 | this.constantValue = config.constantValue; 42 | this.asArray = config.asArray; 43 | this.aggregator = config.aggregator; 44 | this.table = config.table; 45 | this.value = config.getValue(); 46 | this.dataType = config.dataType; 47 | this.isDistinct = config.isDistinct; 48 | this.primaryKey = config.primaryKey; 49 | this.notNull = config.notNull; 50 | this.defaultValue = config.defaultValue; 51 | this.references = config.references; 52 | // If subfieldContainer is present, this is a subfield and subfieldContainer 53 | // is the parent Column 54 | this.subfieldContainer = config.subfieldContainer; 55 | this.subfields = config.subfields; 56 | this.autoGenerated = !!config.autoGenerated; 57 | this.unique = !!config.unique; 58 | } 59 | public distinct(): this { 60 | this.isDistinct = true; 61 | return this; 62 | } 63 | public as(alias: string): this { 64 | this.alias = alias; 65 | return this; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/node/create.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class CreateNode extends Node { 4 | public options: { isTemporary: boolean }; 5 | 6 | constructor(isTemporary: boolean) { 7 | super('CREATE'); 8 | 9 | this.options = { isTemporary }; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/node/createIndex.ts: -------------------------------------------------------------------------------- 1 | import sliced from 'sliced'; 2 | import { Column } from '../column.js'; 3 | import { IndexCreationQuery, Table } from '../table.js'; 4 | import { Node } from './node.js'; 5 | 6 | export class CreateIndexNode extends Node implements IndexCreationQuery { 7 | public table: Table; 8 | public options: { 9 | indexName: string; 10 | columns: Column[]; 11 | type?: string; 12 | algorithm?: string; 13 | parser?: string; 14 | ifNotExists?: boolean; 15 | }; 16 | 17 | constructor(table: Table, indexName: string) { 18 | super('CREATE INDEX'); 19 | 20 | this.table = table; 21 | this.options = { indexName, columns: [] }; 22 | } 23 | 24 | public unique() { 25 | this.options.type = 'unique'; 26 | return this; 27 | } 28 | 29 | public spatial() { 30 | this.options.type = 'spatial'; 31 | return this; 32 | } 33 | 34 | public fulltext() { 35 | this.options.type = 'fulltext'; 36 | return this; 37 | } 38 | 39 | public using(algorithm: string) { 40 | this.options.algorithm = algorithm; 41 | return this; 42 | } 43 | 44 | public on(...columns: Column[]) { 45 | const args = sliced(columns); 46 | this.options.columns = this.options.columns.concat(args); 47 | return this; 48 | } 49 | 50 | public withParser(parser: string) { 51 | this.options.parser = parser; 52 | return this; 53 | } 54 | 55 | public indexName() { 56 | let result = this.options.indexName; 57 | 58 | if (!result) { 59 | const columns = this.options.columns.map((col) => (col.name ? col.name : col.value.name)).sort(); 60 | 61 | result = [this.table.getName(), ...columns].join('_'); 62 | } 63 | 64 | return result; 65 | } 66 | 67 | public ifNotExists() { 68 | this.options.ifNotExists = true; 69 | return this; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/node/createView.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class CreateViewNode extends Node { 4 | public options: { viewName: string }; 5 | 6 | constructor(viewName: string) { 7 | super('CREATE VIEW'); 8 | 9 | this.options = { viewName }; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/node/default.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class DefaultNode extends Node { 4 | constructor() { 5 | super('DEFAULT'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/delete.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class DeleteNode extends Node { 4 | constructor() { 5 | super('DELETE'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/distinct.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class DistinctNode extends Node { 4 | constructor() { 5 | super('DISTINCT'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/distinctOn.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class DistinctOnNode extends Node { 4 | constructor() { 5 | super('DISTINCT ON'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/drop.ts: -------------------------------------------------------------------------------- 1 | import { Table } from '../table.js'; 2 | import { Node } from './node.js'; 3 | 4 | export class DropNode extends Node { 5 | constructor(table: Table) { 6 | super('DROP'); 7 | this.add(table); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/node/dropColumn.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class DropColumnNode extends Node { 4 | constructor() { 5 | super('DROP COLUMN'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/dropIndex.ts: -------------------------------------------------------------------------------- 1 | import { Column } from '../column.js'; 2 | import { Table } from '../table.js'; 3 | import { Node } from './node.js'; 4 | 5 | export class DropIndexNode extends Node { 6 | public table: Table; 7 | public options: { indexName: string, ifExists?: boolean }; 8 | 9 | constructor(table: Table, indexName: string | string[] | Column[]) { 10 | super('DROP INDEX'); 11 | 12 | if (!indexName) { 13 | throw new Error('No index defined!'); 14 | } else if (Array.isArray(indexName) && typeof indexName[0] === 'string') { 15 | indexName = indexName[0] as string; 16 | } else if (Array.isArray(indexName)) { 17 | const columns = (indexName as Column[]).map((col) => col.name).sort(); 18 | indexName = [table.getName()].concat(columns).join('_'); 19 | } 20 | 21 | this.table = table; 22 | this.options = { indexName }; 23 | } 24 | 25 | public ifExists(): DropIndexNode { 26 | this.options.ifExists = true; 27 | return this 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/node/forShare.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class ForShareNode extends Node { 4 | constructor() { 5 | super('FOR SHARE'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/forUpdate.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class ForUpdateNode extends Node { 4 | constructor() { 5 | super('FOR UPDATE'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/foreignKey.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class ForeignKeyNode extends Node { 4 | public name?: string; 5 | public columns: string[]; 6 | public schema?: string; 7 | public table: string; 8 | public refColumns?: string[]; 9 | public onUpdate?: string; 10 | public onDelete?: string; 11 | public constraint?: string; 12 | constructor(config: { 13 | table: string; 14 | columns: string[]; 15 | refColumns?: string[]; 16 | onDelete?: string; 17 | onUpdate?: string; 18 | name?: string; 19 | schema?: string; 20 | constraint?: string; 21 | }) { 22 | super('FOREIGN KEY'); 23 | this.name = config.name; 24 | this.columns = config.columns; 25 | this.schema = config.schema; 26 | this.table = config.table; 27 | this.refColumns = config.refColumns; 28 | this.onUpdate = config.onUpdate; 29 | this.onDelete = config.onDelete; 30 | this.constraint = config.constraint; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/node/from.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class FromNode extends Node { 4 | public skipFromStatement: boolean = false; 5 | constructor() { 6 | super('FROM'); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/node/functionCall.ts: -------------------------------------------------------------------------------- 1 | import { AliasMixin } from './alias.js'; 2 | import { ParameterNode } from './parameter.js'; 3 | import { ValueExpressionNode } from './_internal.js'; 4 | 5 | export class FunctionCallNode extends ValueExpressionNode { 6 | public name: string; 7 | constructor(name: string, args: any[]) { 8 | super('FUNCTION CALL'); 9 | this.name = name; 10 | this.addAll(args.map(ParameterNode.getNodeOrParameterNode)); 11 | } 12 | 13 | public as = AliasMixin.as; 14 | } 15 | -------------------------------------------------------------------------------- /lib/node/groupBy.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class GroupByNode extends Node { 4 | constructor() { 5 | super('GROUP BY'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/having.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class HavingNode extends Node { 4 | constructor() { 5 | super('HAVING'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/ifExists.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class IfExistsNode extends Node { 4 | constructor() { 5 | super('IF EXISTS'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/ifNotExists.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class IfNotExistsNode extends Node { 4 | constructor() { 5 | super('IF NOT EXISTS'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/in.ts: -------------------------------------------------------------------------------- 1 | import { AliasMixin } from './alias.js'; 2 | import { Node } from './node.js'; 3 | import { classMap, ValueExpressionNode } from './_internal.js'; 4 | 5 | export class InNode extends ValueExpressionNode { 6 | public left: Node; 7 | public right: Node | Node[]; 8 | constructor(config: { left: Node; right: Node | Node[] }) { 9 | super('IN'); 10 | this.left = config.left; 11 | this.right = config.right; 12 | } 13 | 14 | public as = AliasMixin.as; 15 | } 16 | 17 | classMap.set('IN', InNode); 18 | -------------------------------------------------------------------------------- /lib/node/indexes.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | import { Table } from '../table.js'; 3 | 4 | export class IndexesNode extends Node { 5 | public table: Table; 6 | 7 | constructor(table: Table) { 8 | super('INDEXES'); 9 | 10 | this.table = table; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/node/insert.ts: -------------------------------------------------------------------------------- 1 | import { Column } from '../column.js'; 2 | import { INodeable } from '../nodeable.js'; 3 | import { ColumnNode } from './column.js'; 4 | import { DefaultNode } from './default.js'; 5 | import { Node } from './node.js'; 6 | import { ParameterNode } from './parameter.js'; 7 | 8 | export class InsertNode extends Node { 9 | public names: string[]; 10 | public columns: ColumnNode[]; 11 | public valueSets: { [key: string]: ColumnNode }[]; 12 | constructor() { 13 | super('INSERT'); 14 | this.names = []; 15 | this.columns = []; 16 | this.valueSets = []; 17 | } 18 | 19 | public add(nodes: Column[] | Node | INodeable | string): this { 20 | if (!Array.isArray(nodes)) { 21 | throw new Error('Not an array of column instances'); 22 | } 23 | let hasColumns = false; 24 | let hasValues = false; 25 | const values: { [key: string]: ColumnNode } = {}; 26 | nodes.forEach((node) => { 27 | const column = node.toNode(); 28 | const name = column.name; 29 | const idx = this.names.indexOf(name); 30 | if (idx < 0) { 31 | this.names.push(name); 32 | this.columns.push(column); 33 | } 34 | hasColumns = true; 35 | hasValues = hasValues || column.value !== undefined; 36 | values[name] = column; 37 | }); 38 | 39 | // When none of the columns have a value, it's ambiguous whether the user 40 | // intends to insert a row of default values or append a SELECT statement 41 | // later. Resolve the ambiguity by assuming that if no columns are specified 42 | // it is a row of default values, otherwise a SELECT will be added. 43 | if (hasValues || !hasColumns) { 44 | this.valueSets.push(values); 45 | } 46 | 47 | return this; 48 | } 49 | 50 | /* 51 | * Get parameters for all values to be inserted. This function 52 | * handles handles bulk inserts, where keys may be present 53 | * in some objects and not others. When keys are not present, 54 | * the insert should refer to the column value as DEFAULT. 55 | */ 56 | public getParameters() { 57 | return this.valueSets.map((nodeDict) => { 58 | const set: Node[] = []; 59 | this.names.forEach((name) => { 60 | const node = nodeDict[name]; 61 | if (node) { 62 | set.push(ParameterNode.getNodeOrParameterNode(node.value)); 63 | } else { 64 | set.push(new DefaultNode()); 65 | } 66 | }); 67 | return set; 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/node/interval.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | interface Interval { 4 | years: number; 5 | months: number; 6 | days: number; 7 | hours: number; 8 | minutes: number; 9 | seconds: number; 10 | } 11 | 12 | export class IntervalNode extends Node { 13 | public years: number; 14 | public months: number; 15 | public days: number; 16 | public hours: number; 17 | public minutes: number; 18 | public seconds: number; 19 | constructor(args: Interval[]) { 20 | super('INTERVAL'); 21 | const interval = args[0] || {}; 22 | this.years = interval.years; 23 | this.months = interval.months; 24 | this.days = interval.days; 25 | this.hours = interval.hours; 26 | this.minutes = interval.minutes; 27 | this.seconds = interval.seconds; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/node/join.ts: -------------------------------------------------------------------------------- 1 | import { INodeable } from '../nodeable.js'; 2 | import { hasTable } from '../tableTracker.js'; 3 | import { Node } from './node.js'; 4 | 5 | export class JoinNode extends Node { 6 | public subType: string; 7 | public from: Node; 8 | public to: Node; 9 | public onNode!: Node; 10 | constructor(subType: string, from: INodeable, to: INodeable) { 11 | super('JOIN'); 12 | this.sql = (hasTable(from) && from.table.sql) || (hasTable(to) && to.table.sql) || undefined; 13 | this.subType = subType; 14 | this.from = from.toNode(); 15 | this.to = to.toNode(); 16 | } 17 | public on(node: Node) { 18 | this.onNode = node; 19 | return this; 20 | } 21 | public join(other: INodeable) { 22 | return new JoinNode('INNER', this, other); 23 | } 24 | public leftJoin(other: INodeable) { 25 | return new JoinNode('LEFT', this, other); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/node/literal.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class LiteralNode extends Node { 4 | public literal: string; 5 | public alias?: string; 6 | constructor(literal: string) { 7 | super('LITERAL'); 8 | this.literal = literal; 9 | this.alias = undefined; 10 | } 11 | public as(alias: string) { 12 | this.alias = alias; 13 | return this; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/node/modifier.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | import { ParameterNode } from './parameter.js'; 3 | import { Query } from './query.js'; 4 | 5 | export class ModifierNode extends Node { 6 | public query: Query; 7 | public count: Node; 8 | constructor(query: Query, type: 'LIMIT' | 'OFFSET', count: unknown) { 9 | super(type); 10 | this.query = query; 11 | this.count = ParameterNode.getNodeOrParameterNode(count); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/node/node.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | import assert from 'assert'; 3 | import { DEFAULT_DIALECT, getDialect } from '../dialect/mapper.js'; 4 | import type { Dialect } from '../dialect/dialect.js'; 5 | import { INodeable, instanceofINodeable } from '../nodeable.js'; 6 | import type { Sql } from '../sql.js'; 7 | import type { Query } from './query.js'; 8 | 9 | export abstract class Node implements INodeable { 10 | public sql?: Sql; 11 | public readonly type: string; 12 | public nodes: Node[]; 13 | constructor(type: string) { 14 | this.type = type; 15 | this.nodes = []; 16 | } 17 | public toNode() { 18 | return this; 19 | } 20 | public add(node: Node | INodeable) { 21 | assert(node, 'Error while trying to add a non-existent node to a query'); 22 | if (!instanceofINodeable(node)) { 23 | throw new Error('Expected Node or INodeable, got ' + typeof node); 24 | } 25 | this.nodes.push(node.toNode()); 26 | return this; 27 | } 28 | public unshift(node: Node | INodeable) { 29 | assert(node, 'Error while trying to add a non-existent node to a query'); 30 | if (!instanceofINodeable(node)) { 31 | throw new Error('Expected Node or INodeable, got ' + typeof node); 32 | } 33 | this.nodes.unshift(node.toNode()); 34 | return this; 35 | } 36 | public toQuery(dialect?: string) { 37 | const DialectClass = determineDialect(this, dialect); 38 | return initializeDialect(DialectClass, this).getQuery(this as unknown as Query); 39 | } 40 | public toNamedQuery(name: string, dialect?: string) { 41 | if (!name || typeof name !== 'string' || name === '') { 42 | throw new Error('A query name has to be a non-empty String.'); 43 | } 44 | const query = this.toQuery(dialect); 45 | return { ...query, name }; 46 | } 47 | public toString(dialect?: string) { 48 | const DialectClass = determineDialect(this, dialect); 49 | return initializeDialect(DialectClass, this).getString(this as unknown as Query); 50 | } 51 | public addAll(nodes: (Node | INodeable)[]) { 52 | for (let i = 0, len = nodes.length; i < len; i++) { 53 | this.add(nodes[i]); 54 | } 55 | return this; 56 | } 57 | } 58 | 59 | // Before the change that introduced parallel dialects, every node could be turned 60 | // into a query. The parallel dialects change made it impossible to change some nodes 61 | // into a query because not all nodes are constructed with the sql instance. 62 | const determineDialect = (query: any, dialect?: string): new (...args: any[]) => Dialect => { 63 | const sql = query.sql || (query.table && query.table.sql); 64 | let DialectClass; 65 | 66 | if (dialect) { 67 | // dialect is specified 68 | DialectClass = getDialect(dialect); 69 | } else if (sql && sql.dialect) { 70 | // dialect is not specified, use the dialect from the sql instance 71 | DialectClass = sql.dialect; 72 | } else { 73 | // dialect is not specified, use the default dialect 74 | DialectClass = getDialect(DEFAULT_DIALECT); 75 | } 76 | return DialectClass; 77 | }; 78 | 79 | const initializeDialect = Dialect>(DialectClass: T, query: any): Dialect => { 80 | const sql = query.sql || (query.table && query.table.sql); 81 | const config = sql ? sql.config : {}; 82 | return new DialectClass(config); 83 | }; 84 | -------------------------------------------------------------------------------- /lib/node/notIn.ts: -------------------------------------------------------------------------------- 1 | import { AliasMixin } from './alias.js'; 2 | import { Node } from './node.js'; 3 | import { classMap, ValueExpressionNode } from './_internal.js'; 4 | 5 | export class NotInNode extends ValueExpressionNode { 6 | public left: Node; 7 | public right: Node | Node[]; 8 | constructor(config: { left: Node; right: Node | Node[] }) { 9 | super('NOT IN'); 10 | this.left = config.left; 11 | this.right = config.right; 12 | } 13 | 14 | public as = AliasMixin.as; 15 | } 16 | 17 | classMap.set('NOT IN', NotInNode); 18 | -------------------------------------------------------------------------------- /lib/node/onConflict.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class OnConflictNode extends Node { 4 | public columns?: string[]; 5 | public constraint?: string; 6 | public update?: string[]; 7 | 8 | constructor(config: { columns?: string[]; constraint?: string; update?: string[] } = {}) { 9 | super('ONCONFLICT'); 10 | this.columns = config.columns; 11 | this.constraint = config.constraint; 12 | this.update = config.update; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/node/onDuplicate.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class OnDuplicateNode extends Node { 4 | constructor() { 5 | super('ONDUPLICATE'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/operator.ts: -------------------------------------------------------------------------------- 1 | import { BinaryNode, PostfixUnaryNode, PrefixUnaryNode, TernaryNode } from './_internal.js'; 2 | import { Node } from './node.js'; 3 | import { ParameterNode } from './parameter.js'; 4 | 5 | // Process values, wrapping them in ParameterNode if necessary. 6 | const processParam = (val: unknown): Node => { 7 | if (Array.isArray(val)) { 8 | throw new Error('expected single value'); 9 | } 10 | return ParameterNode.getNodeOrParameterNode(val); 11 | }; 12 | const processParamOrParams = (val: any): Node | Node[] => { 13 | return Array.isArray(val) ? val.map(ParameterNode.getNodeOrParameterNode) : ParameterNode.getNodeOrParameterNode(val); 14 | }; 15 | 16 | export const prefixUnaryOperator = (operator: string) => { 17 | return (left: any): PrefixUnaryNode => { 18 | return new PrefixUnaryNode({ 19 | left: processParam(left), 20 | operator, 21 | }); 22 | }; 23 | }; 24 | 25 | export const postfixUnaryOperator = (operator: string) => { 26 | return (left: any): PostfixUnaryNode => { 27 | return new PostfixUnaryNode({ 28 | left: processParam(left), 29 | operator, 30 | }); 31 | }; 32 | }; 33 | 34 | export const binaryOperator = (operator: string) => { 35 | return (left: any, val: any): BinaryNode => { 36 | return new BinaryNode({ 37 | left: processParam(left), 38 | operator, 39 | right: processParamOrParams(val), 40 | }); 41 | }; 42 | }; 43 | 44 | export const ternaryOperator = (operator: string, separator: string) => { 45 | return (left: any, middle: any, right: any): TernaryNode => { 46 | return new TernaryNode({ 47 | left: processParam(left), 48 | operator, 49 | middle: processParam(middle), 50 | separator, 51 | right: processParam(right), 52 | }); 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /lib/node/orIgnore.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class OrIgnoreNode extends Node { 4 | constructor() { 5 | super('OR IGNORE'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/orderBy.ts: -------------------------------------------------------------------------------- 1 | import { ModifierNode } from './modifier.js'; 2 | import { Node } from './node.js'; 3 | 4 | export class OrderByNode extends Node { 5 | public msSQLOffsetNode?: ModifierNode; 6 | public msSQLLimitNode?: ModifierNode; 7 | constructor() { 8 | super('ORDER BY'); 9 | // used when processing OFFSET and LIMIT clauses in MSSQL 10 | this.msSQLOffsetNode = undefined; 11 | this.msSQLLimitNode = undefined; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/node/orderByValue.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | import { TextNode } from './text.js'; 3 | import { classMap } from './valueExpression.js'; 4 | 5 | export class OrderByValueNode extends Node { 6 | public value: Node; 7 | public direction?: TextNode; 8 | constructor(config: { value: Node; direction?: TextNode }) { 9 | super('ORDER BY VALUE'); 10 | this.value = config.value; 11 | this.direction = config.direction; 12 | } 13 | } 14 | 15 | classMap.set('ORDER BY VALUE', OrderByValueNode); 16 | -------------------------------------------------------------------------------- /lib/node/parameter.ts: -------------------------------------------------------------------------------- 1 | import { INodeable, instanceofINodeable } from '../nodeable.js'; 2 | import { Node } from './node.js'; 3 | 4 | export class ParameterNode extends Node { 5 | // wrap a value as a parameter node if value is not already a node 6 | public static getNodeOrParameterNode(value?: INodeable | unknown) { 7 | return value && instanceofINodeable(value) ? value.toNode() : new ParameterNode(value); 8 | } 9 | public isExplicit: boolean; 10 | private val: any; 11 | constructor(val: any) { 12 | super('PARAMETER'); 13 | this.val = val; 14 | this.isExplicit = false; 15 | } 16 | public value() { 17 | return this.val; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/node/postfixUnary.ts: -------------------------------------------------------------------------------- 1 | import { AliasMixin } from './alias.js'; 2 | import { Node } from './node.js'; 3 | import { classMap, ValueExpressionNode } from './_internal.js'; 4 | 5 | export class PostfixUnaryNode extends ValueExpressionNode { 6 | public left: Node; 7 | public operator: string; 8 | constructor(config: { left: Node; operator: string }) { 9 | super('POSTFIX UNARY'); 10 | this.left = config.left; 11 | this.operator = config.operator; 12 | } 13 | 14 | public as = AliasMixin.as; 15 | } 16 | 17 | classMap.set('POSTFIX UNARY', PostfixUnaryNode); 18 | -------------------------------------------------------------------------------- /lib/node/prefixUnary.ts: -------------------------------------------------------------------------------- 1 | import { AliasMixin } from './alias.js'; 2 | import { Node } from './node.js'; 3 | import { classMap, ValueExpressionNode } from './_internal.js'; 4 | 5 | export class PrefixUnaryNode extends ValueExpressionNode { 6 | public left: Node; 7 | public operator: string; 8 | constructor(config: { left: Node; operator: string }) { 9 | super('PREFIX UNARY'); 10 | this.left = config.left; 11 | this.operator = config.operator; 12 | } 13 | 14 | public as = AliasMixin.as; 15 | } 16 | 17 | classMap.set('PREFIX UNARY', PrefixUnaryNode); 18 | -------------------------------------------------------------------------------- /lib/node/rename.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class RenameNode extends Node { 4 | constructor() { 5 | super('RENAME'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/renameColumn.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class RenameColumnNode extends Node { 4 | constructor() { 5 | super('RENAME COLUMN'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/replace.ts: -------------------------------------------------------------------------------- 1 | import { Column } from '../column.js'; 2 | import { INodeable } from '../nodeable.js'; 3 | import { ColumnNode } from './column.js'; 4 | import { DefaultNode } from './default.js'; 5 | import { Node } from './node.js'; 6 | import { ParameterNode } from './parameter.js'; 7 | 8 | export class ReplaceNode extends Node { 9 | public columns: ColumnNode[]; 10 | public names: string[]; 11 | public valueSets: { [key: string]: ColumnNode }[]; 12 | constructor() { 13 | super('REPLACE'); 14 | this.names = []; 15 | this.columns = []; 16 | this.valueSets = []; 17 | } 18 | 19 | public add(nodes: Column[] | Node | INodeable | string): this { 20 | if (!Array.isArray(nodes)) { 21 | throw new Error('Not an array of column instances'); 22 | } 23 | let hasColumns = false; 24 | let hasValues = false; 25 | const values: { [key: string]: ColumnNode } = {}; 26 | nodes.forEach((node) => { 27 | const column = node.toNode() as ColumnNode; 28 | const name = column.name; 29 | const idx = this.names.indexOf(name); 30 | if (idx < 0) { 31 | this.names.push(name); 32 | this.columns.push(column); 33 | } 34 | hasColumns = true; 35 | hasValues = hasValues || column.value !== undefined; 36 | values[name] = column; 37 | }); 38 | 39 | // When none of the columns have a value, it's ambiguous whether the user 40 | // intends to replace a row of default values or append a SELECT statement 41 | // later. Resolve the ambiguity by assuming that if no columns are specified 42 | // it is a row of default values, otherwise a SELECT will be added. 43 | if (hasValues || !hasColumns) { 44 | this.valueSets.push(values); 45 | } 46 | 47 | return this; 48 | } 49 | 50 | /* 51 | * Get parameters for all values to be replaced. This function 52 | * handles handles bulk replaces, where keys may be present 53 | * in some objects and not others. When keys are not present, 54 | * the replace should refer to the column value as DEFAULT. 55 | */ 56 | public getParameters() { 57 | return this.valueSets.map((nodeDict) => { 58 | const set: Node[] = []; 59 | this.names.forEach((name) => { 60 | const node = nodeDict[name]; 61 | if (node) { 62 | set.push(ParameterNode.getNodeOrParameterNode(node.value)); 63 | } else { 64 | set.push(new DefaultNode()); 65 | } 66 | }); 67 | return set; 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/node/restrict.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class RestrictNode extends Node { 4 | constructor() { 5 | super('RESTRICT'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/returning.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class ReturningNode extends Node { 4 | constructor() { 5 | super('RETURNING'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/rowCall.ts: -------------------------------------------------------------------------------- 1 | import flatten from 'lodash/flatten.js'; 2 | import { AliasMixin } from './alias.js'; 3 | import { ParameterNode } from './parameter.js'; 4 | import { ValueExpressionNode } from './_internal.js'; 5 | 6 | export class RowCallNode extends ValueExpressionNode { 7 | constructor(args: any[]) { 8 | super('ROW CALL'); 9 | args = flatten(args); 10 | this.addAll(args.map(ParameterNode.getNodeOrParameterNode)); 11 | } 12 | 13 | public as = AliasMixin.as; 14 | } 15 | -------------------------------------------------------------------------------- /lib/node/select.ts: -------------------------------------------------------------------------------- 1 | import { ModifierNode } from './modifier.js'; 2 | import { Node } from './node.js'; 3 | 4 | export class SelectNode extends Node { 5 | public msSQLLimitNode?: ModifierNode; 6 | public isDistinct: boolean; 7 | constructor() { 8 | super('SELECT'); 9 | // used when processing LIMIT clauses in MSSQL 10 | this.msSQLLimitNode = undefined; 11 | // set to true when a DISTINCT is used on the entire result set 12 | this.isDistinct = false; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/node/slice.ts: -------------------------------------------------------------------------------- 1 | import { AliasMixin } from './alias.js'; 2 | import { Node } from './node.js'; 3 | import { classMap, ValueExpressionNode } from './_internal.js'; 4 | 5 | export class SliceNode extends ValueExpressionNode { 6 | public value: Node; 7 | public start: Node; 8 | public end: Node; 9 | constructor(value: Node, start: Node, end: Node) { 10 | super('SLICE'); 11 | this.value = value; 12 | this.start = start; 13 | this.end = end; 14 | } 15 | 16 | public as = AliasMixin.as; 17 | } 18 | 19 | classMap.set('SLICE', SliceNode); 20 | -------------------------------------------------------------------------------- /lib/node/table.ts: -------------------------------------------------------------------------------- 1 | import { Table } from '../table.js'; 2 | import { Node } from './node.js'; 3 | 4 | export class TableNode extends Node { 5 | public table: Table; 6 | constructor(table: Table) { 7 | super('TABLE'); 8 | this.table = table; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/node/ternary.ts: -------------------------------------------------------------------------------- 1 | import { AliasMixin } from './alias.js'; 2 | import { Node } from './node.js'; 3 | import { classMap, ValueExpressionNode } from './_internal.js'; 4 | 5 | export class TernaryNode extends ValueExpressionNode { 6 | public left: Node; 7 | public middle: Node; 8 | public operator: string; 9 | public right: Node; 10 | public separator: string; 11 | constructor(config: { left: Node; middle: Node; operator: string; right: Node; separator: string }) { 12 | super('TERNARY'); 13 | this.left = config.left; 14 | this.middle = config.middle; 15 | this.operator = config.operator; 16 | this.right = config.right; 17 | this.separator = config.separator; 18 | } 19 | 20 | public as = AliasMixin.as; 21 | } 22 | 23 | classMap.set('TERNARY', TernaryNode); 24 | -------------------------------------------------------------------------------- /lib/node/text.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class TextNode extends Node { 4 | public text: string; 5 | constructor(text: string) { 6 | super('TEXT'); 7 | this.text = text; 8 | } 9 | } -------------------------------------------------------------------------------- /lib/node/truncate.ts: -------------------------------------------------------------------------------- 1 | import { Table } from '../table.js'; 2 | import { Node } from './node.js'; 3 | 4 | export class TruncateNode extends Node { 5 | constructor(table: Table) { 6 | super('TRUNCATE'); 7 | this.add(table); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/node/update.ts: -------------------------------------------------------------------------------- 1 | import { Node } from './node.js'; 2 | 3 | export class UpdateNode extends Node { 4 | constructor() { 5 | super('UPDATE'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/node/where.ts: -------------------------------------------------------------------------------- 1 | import { INodeable, instanceofINodeable } from '../nodeable.js'; 2 | import { Table } from '../table.js'; 3 | import { BinaryNode } from './binary.js'; 4 | import { Node } from './node.js'; 5 | import { TextNode } from './text.js'; 6 | 7 | const normalizeNode = (table: Table, node: Node[] | Node | INodeable | Record | string): Node => { 8 | if (typeof node === 'string') { 9 | return new TextNode(`(${node})`); 10 | } else if (Array.isArray(node)) { 11 | if (node.length === 0) { 12 | return new TextNode('(1 = 1)'); 13 | } else { 14 | let result: Node | undefined; 15 | for (const subNode of node) { 16 | result = !result ? subNode : (result as any).and(subNode); 17 | } 18 | return result!; 19 | } 20 | } else if (!instanceofINodeable(node) && typeof node === 'object') { 21 | let result: Node | undefined; 22 | for (const colName in node) { 23 | if (colName in node) { 24 | const column = table.getColumn(colName)!; 25 | const query = column.equals((node as any)[colName]); 26 | result = !result ? query : (result as any).and(query); 27 | } 28 | } 29 | return result!; 30 | } else { 31 | return node.toNode(); 32 | } 33 | }; 34 | 35 | export class WhereNode extends Node { 36 | public table: Table; 37 | constructor(table: Table) { 38 | super('WHERE'); 39 | this.table = table; 40 | } 41 | 42 | public add(node: Node[] | Node | INodeable | Record | string) { 43 | const add = normalizeNode(this.table, node); 44 | return super.add(add); 45 | } 46 | 47 | public or(other: Node | Record | string): void { 48 | const right = normalizeNode(this.table, other); 49 | // calling 'or' without an initial 'where' 50 | if (!this.nodes.length) { 51 | this.add(other); 52 | } else { 53 | this.nodes.push( 54 | new BinaryNode({ 55 | left: this.nodes.pop()!, 56 | operator: 'OR', 57 | right 58 | }) 59 | ); 60 | } 61 | } 62 | 63 | public and(other: Node[] | Node | Record | string): void { 64 | const right = normalizeNode(this.table, other); 65 | this.nodes.push( 66 | new BinaryNode({ 67 | left: this.nodes.pop()!, 68 | operator: 'AND', 69 | right 70 | }) 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/nodeable.ts: -------------------------------------------------------------------------------- 1 | import type { Node } from './node/node.js'; 2 | 3 | export interface INodeable { 4 | toNode(): Node; 5 | } 6 | 7 | // eslint-disable-next-line prefer-arrow/prefer-arrow-functions 8 | export function instanceofINodeable(o: unknown): o is INodeable { 9 | return typeof o === 'object' && o !== null && 'toNode' in o; 10 | } 11 | 12 | export type PartialNodeable = { [P in keyof T]?: T[P] | INodeable | Buffer | null }; 13 | -------------------------------------------------------------------------------- /lib/sql.ts: -------------------------------------------------------------------------------- 1 | import defaults from 'lodash/defaults.js'; 2 | import sliced from 'sliced'; 3 | import { Column } from './column.js'; 4 | import { ColumnDefinition, SQLDialects, TableDefinition } from './configTypes.js'; 5 | import { DEFAULT_DIALECT, getDialect } from './dialect/mapper.js'; 6 | import * as functions from './functions.js'; 7 | import { ArrayCallNode } from './node/arrayCall.js'; 8 | import { FunctionCallNode } from './node/functionCall.js'; 9 | import { IntervalNode } from './node/interval.js'; 10 | import { LiteralNode } from './node/literal.js'; 11 | import { ParameterNode } from './node/parameter.js'; 12 | import { Query } from './node/query.js'; 13 | import { RowCallNode } from './node/rowCall.js'; 14 | import { Table, TableWithColumns } from './table.js'; 15 | import { 16 | binaryOperator, 17 | postfixUnaryOperator, 18 | prefixUnaryOperator, 19 | ternaryOperator 20 | } from './node/operator.js'; 21 | 22 | export class Sql { 23 | public functions: functions.StandardFunctions; 24 | public dialect: any; 25 | public dialectName!: SQLDialects; 26 | public config: any; 27 | private _function: any; 28 | constructor(dialect: SQLDialects = DEFAULT_DIALECT, config: any = {}) { 29 | this.setDialect(dialect, config); 30 | // attach the standard SQL functions to this instance 31 | this.functions = functions.getStandardFunctions(); 32 | this._function = functions.getFunctions; 33 | } 34 | // Define a function 35 | public function(functionNames: string[]): { [key: string]: (...args: any[]) => FunctionCallNode }; 36 | public function(functionName: string): (...args: any[]) => FunctionCallNode; 37 | public function(...args: any[]): any { 38 | return this._function(...args); 39 | } 40 | // Define an operator 41 | public prefixUnaryOperator = prefixUnaryOperator; 42 | public postfixUnaryOperator = postfixUnaryOperator; 43 | public binaryOperator = binaryOperator; 44 | public ternaryOperator = ternaryOperator; 45 | // Define a table 46 | public define(def: TableDefinition): TableWithColumns { 47 | def = defaults(def || {}, { 48 | sql: this 49 | }); 50 | return Table.define(def); 51 | } 52 | public defineColumn(def: ColumnDefinition): Column { 53 | return new Column(def); 54 | } 55 | // Returns a bracketed call creator literal 56 | public array(...args: any[]) { 57 | const arrayCall = new ArrayCallNode(sliced(args)); 58 | return arrayCall; 59 | } 60 | // Returns a bracketed call creator literal 61 | public row(...args: any[]) { 62 | const rowCall = new RowCallNode(sliced(args)); 63 | return rowCall; 64 | } 65 | // Returns a select statement 66 | public select(...args: any[]) { 67 | const query = new Query({ sql: this } as any); 68 | query.select(...args); 69 | return query; 70 | } 71 | // Returns an interval clause 72 | public interval(...args: any[]) { 73 | const interval = new IntervalNode(sliced(args)); 74 | return interval; 75 | } 76 | // Set the dialect 77 | public setDialect(dialect: SQLDialects, config: any = {}) { 78 | this.dialect = getDialect(dialect); 79 | this.dialectName = dialect; 80 | this.config = config; 81 | return this; 82 | } 83 | // Create a constant Column (for use in SELECT) 84 | public constant(value: any) { 85 | const config = { 86 | constantValue: value, 87 | isConstant: true, 88 | name: 'constant', 89 | property: 'constant' 90 | }; 91 | const cn = new Column(config); 92 | return cn; 93 | } 94 | // Create a literal 95 | public literal(literal: any): LiteralNode { 96 | return new LiteralNode(literal); 97 | } 98 | // Create a parameter 99 | public parameter(value: any): ParameterNode { 100 | return new ParameterNode(value); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/tableTracker.ts: -------------------------------------------------------------------------------- 1 | import { Table } from './table.js'; 2 | 3 | export interface ITableTracker { 4 | table?: Table; 5 | } 6 | 7 | // eslint-disable-next-line prefer-arrow/prefer-arrow-functions 8 | export function hasTable(o: unknown): o is Required { 9 | return typeof o === 'object' && o !== null && 'table' in o; 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Stefan Charsley", 4 | "email": "charsleysa@gmail.com" 5 | }, 6 | "name": "sql-ts", 7 | "description": "SQL Builder", 8 | "version": "7.1.0", 9 | "homepage": "https://github.com/charsleysa/node-sql-ts", 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/charsleysa/node-sql-ts.git" 14 | }, 15 | "type": "module", 16 | "exports": { 17 | ".": { 18 | "import": "./dist/lib.js", 19 | "require": "./dist/lib.cjs", 20 | "types": "./dist/lib.d.ts" 21 | } 22 | }, 23 | "types": "./dist/lib.d.ts", 24 | "scripts": { 25 | "pretest": "eslint --ext .ts ./lib && eslint --ext .ts ./test && rollup -c && tsc -p ./test/tsconfig.json", 26 | "test": "mocha", 27 | "lint": "eslint --ext .ts ./lib && eslint --ext .ts ./test", 28 | "build": "rollup -c", 29 | "release:major": "npm run test && npm run build && npm version major -m \"chore: release\" && git push origin master --tags && npm publish", 30 | "release:minor": "npm run test && npm run build && npm version minor -m \"chore: release\" && git push origin master --tags && npm publish", 31 | "release:patch": "npm run test && npm run build && npm version patch -m \"chore: release\" && git push origin master --tags && npm publish" 32 | }, 33 | "engines": { 34 | "node": "^18 || ^20 || >=22" 35 | }, 36 | "dependencies": { 37 | "lodash": "^4.17.21", 38 | "sliced": "~1.0.x" 39 | }, 40 | "devDependencies": { 41 | "@rollup/plugin-typescript": "^11.1.6", 42 | "@types/lodash": "^4.17.7", 43 | "@types/mocha": "^10.0.8", 44 | "@types/node": "^20.16.5", 45 | "@typescript-eslint/eslint-plugin": "^8.6.0", 46 | "@typescript-eslint/parser": "^8.6.0", 47 | "eslint": "^8.57.1", 48 | "eslint-config-prettier": "^9.1.0", 49 | "eslint-plugin-import": "^2.30.0", 50 | "eslint-plugin-jsdoc": "^50.2.4", 51 | "eslint-plugin-prefer-arrow": "^1.2.3", 52 | "eslint-plugin-unicorn": "^55.0.0", 53 | "mocha": "^10.7.3", 54 | "rollup-plugin-delete": "^2.1.0", 55 | "rollup-plugin-dts": "^6.1.1", 56 | "ts-node": "^10.9.2", 57 | "typescript": "~5.5.0" 58 | }, 59 | "mocha": { 60 | "reporter": "dot", 61 | "ui": "tdd", 62 | "watch-extensions": "js", 63 | "spec": "spec/**/*.js" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript'; 2 | import del from 'rollup-plugin-delete'; 3 | import dts from 'rollup-plugin-dts'; 4 | 5 | export default [ 6 | { 7 | input: './lib/index.ts', 8 | output: { 9 | file: './dist/lib.js', 10 | format: 'esm', 11 | sourcemap: true 12 | }, 13 | external: [ 14 | 'assert', 15 | 'lodash/defaults.js', 16 | 'lodash/flatten.js', 17 | 'lodash/fromPairs.js', 18 | 'lodash/isArray.js', 19 | 'lodash/isNumber.js', 20 | 'lodash/isFunction.js', 21 | 'lodash/map.js', 22 | 'lodash/padStart.js', 23 | 'lodash/reduce.js', 24 | 'sliced' 25 | ], 26 | plugins: [ 27 | del({ 28 | targets: './dist' 29 | }), 30 | typescript({ 31 | tsconfig: './tsconfig.build.json' 32 | }) 33 | ] 34 | }, 35 | { 36 | input: './lib/index.ts', 37 | output: { 38 | file: './dist/lib.cjs', 39 | format: 'commonjs', 40 | sourcemap: true 41 | }, 42 | external: [ 43 | 'assert', 44 | 'lodash/defaults.js', 45 | 'lodash/flatten.js', 46 | 'lodash/fromPairs.js', 47 | 'lodash/isArray.js', 48 | 'lodash/isNumber.js', 49 | 'lodash/isFunction.js', 50 | 'lodash/map.js', 51 | 'lodash/padStart.js', 52 | 'lodash/reduce.js', 53 | 'sliced' 54 | ], 55 | plugins: [ 56 | typescript({ 57 | tsconfig: './tsconfig.build.json' 58 | }) 59 | ] 60 | }, 61 | { 62 | input: './dist/dts/index.d.ts', 63 | output: { 64 | file: './dist/lib.d.ts', 65 | format: 'es' 66 | }, 67 | plugins: [ 68 | dts(), 69 | del({ 70 | hook: 'buildEnd', 71 | targets: './dist/dts' 72 | }) 73 | ] 74 | } 75 | ]; 76 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.eslintrc.json", 3 | "parserOptions": { 4 | "project": [ 5 | "tsconfig.json" 6 | ], 7 | "sourceType": "module" 8 | }, 9 | "rules": { 10 | "prefer-arrow/prefer-arrow-functions": "off", 11 | "id-blacklist": "off", 12 | "import/no-internal-modules": "off" 13 | } 14 | } -------------------------------------------------------------------------------- /test/binary-clause-tests.ts: -------------------------------------------------------------------------------- 1 | import { strictEqual } from 'assert'; 2 | import { Table } from '../dist/lib.js'; 3 | 4 | const Foo = Table.define<{ baz: string; bar: string }>({ 5 | name: 'foo', 6 | columns: ['baz', 'bar'] 7 | }); 8 | 9 | test('operators', function() { 10 | strictEqual(Foo.baz.equals(1).operator, '='); 11 | strictEqual(Foo.baz.notEquals(1).operator, '<>'); 12 | strictEqual(Foo.baz.like('asdf').operator, 'LIKE'); 13 | strictEqual(Foo.baz.notLike('asdf').operator, 'NOT LIKE'); 14 | strictEqual(Foo.baz.isNull().operator, 'IS NULL'); 15 | strictEqual(Foo.baz.isNotNull().operator, 'IS NOT NULL'); 16 | strictEqual(Foo.baz.gt(1).operator, '>'); 17 | strictEqual(Foo.baz.gte(1).operator, '>='); 18 | strictEqual(Foo.baz.lt(1).operator, '<'); 19 | strictEqual(Foo.baz.lte(1).operator, '<='); 20 | strictEqual(Foo.baz.plus(1).operator, '+'); 21 | strictEqual(Foo.baz.minus(1).operator, '-'); 22 | strictEqual(Foo.baz.multiply(1).operator, '*'); 23 | strictEqual(Foo.baz.leftShift(1).operator, '<<'); 24 | strictEqual(Foo.baz.rightShift(1).operator, '>>'); 25 | strictEqual(Foo.baz.bitwiseAnd(1).operator, '&'); 26 | strictEqual(Foo.baz.bitwiseNot(1).operator, '~'); 27 | strictEqual(Foo.baz.bitwiseOr(1).operator, '|'); 28 | strictEqual(Foo.baz.bitwiseXor(1).operator, '#'); 29 | strictEqual(Foo.baz.divide(1).operator, '/'); 30 | strictEqual(Foo.baz.modulo(1).operator, '%'); 31 | strictEqual(Foo.baz.regex(1).operator, '~'); 32 | strictEqual(Foo.baz.iregex(1).operator, '~*'); 33 | strictEqual(Foo.baz.notRegex(1).operator, '!~'); 34 | strictEqual(Foo.baz.notIregex(1).operator, '!~*'); 35 | strictEqual(Foo.baz.regexp(1).operator, 'REGEXP'); 36 | strictEqual(Foo.baz.rlike(1).operator, 'RLIKE'); 37 | strictEqual(Foo.baz.ilike('asdf').operator, 'ILIKE'); 38 | strictEqual(Foo.baz.notIlike('asdf').operator, 'NOT ILIKE'); 39 | strictEqual(Foo.baz.match('asdf').operator, '@@'); 40 | }); 41 | -------------------------------------------------------------------------------- /test/clause-definition.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | import { strictEqual } from 'assert'; 3 | 4 | import { Node } from '../dist/lib.js'; 5 | 6 | class Bang extends Node { 7 | constructor() { 8 | super('SELECT'); 9 | } 10 | } 11 | 12 | class Boom extends Node { 13 | name: string; 14 | constructor(n: string) { 15 | super('BOOM'); 16 | this.name = n; 17 | } 18 | } 19 | 20 | test('clause definition', function() { 21 | const select = new Bang(); 22 | strictEqual(select.type, 'SELECT'); 23 | strictEqual(select.nodes.length, 0); 24 | 25 | const q = new Boom('hai'); 26 | strictEqual(q.nodes.length, 0); 27 | const q2 = new Boom('bai'); 28 | q.nodes.push(select); 29 | strictEqual(q.nodes.length, 1); 30 | strictEqual(q.name, 'hai'); 31 | strictEqual(q2.nodes.length, 0); 32 | strictEqual(q2.name, 'bai'); 33 | }); 34 | -------------------------------------------------------------------------------- /test/dialects/alias-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | import { Sql } from '../../dist/lib.js'; 3 | const customer = Harness.defineCustomerTable(); 4 | const instance = new Sql('postgres'); 5 | 6 | Harness.test({ 7 | query: customer.select(customer.name.isNull().as('nameIsNull')), 8 | pg: { 9 | text: 'SELECT ("customer"."name" IS NULL) AS "nameIsNull" FROM "customer"', 10 | string: 'SELECT ("customer"."name" IS NULL) AS "nameIsNull" FROM "customer"' 11 | }, 12 | sqlite: { 13 | text: 'SELECT ("customer"."name" IS NULL) AS "nameIsNull" FROM "customer"', 14 | string: 'SELECT ("customer"."name" IS NULL) AS "nameIsNull" FROM "customer"' 15 | }, 16 | mysql: { 17 | text: 'SELECT (`customer`.`name` IS NULL) AS `nameIsNull` FROM `customer`', 18 | string: 'SELECT (`customer`.`name` IS NULL) AS `nameIsNull` FROM `customer`' 19 | }, 20 | mssql: { 21 | text: 'SELECT ([customer].[name] IS NULL) AS [nameIsNull] FROM [customer]', 22 | string: 'SELECT ([customer].[name] IS NULL) AS [nameIsNull] FROM [customer]' 23 | }, 24 | oracle: { 25 | text: 'SELECT ("customer"."name" IS NULL) "nameIsNull" FROM "customer"', 26 | string: 'SELECT ("customer"."name" IS NULL) "nameIsNull" FROM "customer"' 27 | }, 28 | params: [] 29 | }); 30 | 31 | Harness.test({ 32 | query: customer.select(customer.name.plus(customer.age).as('nameAndAge')).where(customer.age.gt(10).and(customer.age.lt(20))), 33 | pg: { 34 | text: 35 | 'SELECT ("customer"."name" + "customer"."age") AS "nameAndAge" FROM "customer" WHERE (("customer"."age" > $1) AND ("customer"."age" < $2))', 36 | string: 37 | 'SELECT ("customer"."name" + "customer"."age") AS "nameAndAge" FROM "customer" WHERE (("customer"."age" > 10) AND ("customer"."age" < 20))' 38 | }, 39 | sqlite: { 40 | text: 41 | 'SELECT ("customer"."name" + "customer"."age") AS "nameAndAge" FROM "customer" WHERE (("customer"."age" > $1) AND ("customer"."age" < $2))', 42 | string: 43 | 'SELECT ("customer"."name" + "customer"."age") AS "nameAndAge" FROM "customer" WHERE (("customer"."age" > 10) AND ("customer"."age" < 20))' 44 | }, 45 | mysql: { 46 | text: 47 | 'SELECT (`customer`.`name` + `customer`.`age`) AS `nameAndAge` FROM `customer` WHERE ((`customer`.`age` > ?) AND (`customer`.`age` < ?))', 48 | string: 49 | 'SELECT (`customer`.`name` + `customer`.`age`) AS `nameAndAge` FROM `customer` WHERE ((`customer`.`age` > 10) AND (`customer`.`age` < 20))' 50 | }, 51 | mssql: { 52 | text: 53 | 'SELECT ([customer].[name] + [customer].[age]) AS [nameAndAge] FROM [customer] WHERE (([customer].[age] > @1) AND ([customer].[age] < @2))', 54 | string: 55 | 'SELECT ([customer].[name] + [customer].[age]) AS [nameAndAge] FROM [customer] WHERE (([customer].[age] > 10) AND ([customer].[age] < 20))' 56 | }, 57 | oracle: { 58 | text: 59 | 'SELECT ("customer"."name" + "customer"."age") "nameAndAge" FROM "customer" WHERE (("customer"."age" > :1) AND ("customer"."age" < :2))', 60 | string: 61 | 'SELECT ("customer"."name" + "customer"."age") "nameAndAge" FROM "customer" WHERE (("customer"."age" > 10) AND ("customer"."age" < 20))' 62 | }, 63 | params: [10, 20] 64 | }); 65 | 66 | Harness.test({ 67 | query: customer.select(customer.age.between(10, 20).as('ageBetween')), 68 | pg: { 69 | text: 'SELECT ("customer"."age" BETWEEN $1 AND $2) AS "ageBetween" FROM "customer"', 70 | string: 'SELECT ("customer"."age" BETWEEN 10 AND 20) AS "ageBetween" FROM "customer"' 71 | }, 72 | sqlite: { 73 | text: 'SELECT ("customer"."age" BETWEEN $1 AND $2) AS "ageBetween" FROM "customer"', 74 | string: 'SELECT ("customer"."age" BETWEEN 10 AND 20) AS "ageBetween" FROM "customer"' 75 | }, 76 | mysql: { 77 | text: 'SELECT (`customer`.`age` BETWEEN ? AND ?) AS `ageBetween` FROM `customer`', 78 | string: 'SELECT (`customer`.`age` BETWEEN 10 AND 20) AS `ageBetween` FROM `customer`' 79 | }, 80 | mssql: { 81 | text: 'SELECT ([customer].[age] BETWEEN @1 AND @2) AS [ageBetween] FROM [customer]', 82 | string: 'SELECT ([customer].[age] BETWEEN 10 AND 20) AS [ageBetween] FROM [customer]' 83 | }, 84 | oracle: { 85 | text: 'SELECT ("customer"."age" BETWEEN :1 AND :2) "ageBetween" FROM "customer"', 86 | string: 'SELECT ("customer"."age" BETWEEN 10 AND 20) "ageBetween" FROM "customer"' 87 | }, 88 | params: [10, 20] 89 | }); 90 | 91 | Harness.test({ 92 | query: customer.select(instance.functions.ROUND(customer.age.as('ageBetween'), 2)), 93 | pg: { 94 | text: 'SELECT ROUND("customer"."age", $1) FROM "customer"', 95 | string: 'SELECT ROUND("customer"."age", 2) FROM "customer"' 96 | }, 97 | sqlite: { 98 | text: 'SELECT ROUND("customer"."age", $1) FROM "customer"', 99 | string: 'SELECT ROUND("customer"."age", 2) FROM "customer"' 100 | }, 101 | mysql: { 102 | text: 'SELECT ROUND(`customer`.`age`, ?) FROM `customer`', 103 | string: 'SELECT ROUND(`customer`.`age`, 2) FROM `customer`' 104 | }, 105 | mssql: { 106 | text: 'SELECT ROUND([customer].[age], @1) FROM [customer]', 107 | string: 'SELECT ROUND([customer].[age], 2) FROM [customer]' 108 | }, 109 | oracle: { 110 | text: 'SELECT ROUND("customer"."age", :1) FROM "customer"', 111 | string: 'SELECT ROUND("customer"."age", 2) FROM "customer"' 112 | }, 113 | params: [2] 114 | }); 115 | 116 | Harness.test({ 117 | query: customer.select(customer.age.notBetween(10, 20).as('ageNotBetween')), 118 | pg: { 119 | text: 'SELECT ("customer"."age" NOT BETWEEN $1 AND $2) AS "ageNotBetween" FROM "customer"', 120 | string: 'SELECT ("customer"."age" NOT BETWEEN 10 AND 20) AS "ageNotBetween" FROM "customer"' 121 | }, 122 | sqlite: { 123 | text: 'SELECT ("customer"."age" NOT BETWEEN $1 AND $2) AS "ageNotBetween" FROM "customer"', 124 | string: 'SELECT ("customer"."age" NOT BETWEEN 10 AND 20) AS "ageNotBetween" FROM "customer"' 125 | }, 126 | mysql: { 127 | text: 'SELECT (`customer`.`age` NOT BETWEEN ? AND ?) AS `ageNotBetween` FROM `customer`', 128 | string: 'SELECT (`customer`.`age` NOT BETWEEN 10 AND 20) AS `ageNotBetween` FROM `customer`' 129 | }, 130 | params: [10, 20] 131 | }); 132 | -------------------------------------------------------------------------------- /test/dialects/array-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | import { Sql } from '../../dist/lib.js'; 3 | const post = Harness.definePostTable(); 4 | const instance = new Sql('postgres'); 5 | 6 | // Array columns 7 | Harness.test({ 8 | query: post.update({ 9 | tags: post.tags.concat(instance.array('nodejs')) 10 | }), 11 | pg: { 12 | text: 'UPDATE "post" SET "tags" = ("post"."tags" || ARRAY[$1])', 13 | string: 'UPDATE "post" SET "tags" = ("post"."tags" || ARRAY[\'nodejs\'])' 14 | }, 15 | params: ['nodejs'] 16 | }); 17 | 18 | Harness.test({ 19 | query: post.select(post.tags.contains(instance.array('nodejs', 'js'))), 20 | pg: { 21 | text: 'SELECT ("post"."tags" @> ARRAY[$1, $2]) FROM "post"', 22 | string: 'SELECT ("post"."tags" @> ARRAY[\'nodejs\', \'js\']) FROM "post"' 23 | }, 24 | params: ['nodejs', 'js'] 25 | }); 26 | 27 | Harness.test({ 28 | query: post.select(post.tags.containedBy(instance.array('nodejs', 'js'))), 29 | pg: { 30 | text: 'SELECT ("post"."tags" <@ ARRAY[$1, $2]) FROM "post"', 31 | string: 'SELECT ("post"."tags" <@ ARRAY[\'nodejs\', \'js\']) FROM "post"' 32 | }, 33 | params: ['nodejs', 'js'] 34 | }); 35 | 36 | Harness.test({ 37 | query: post.select(post.tags.overlap(instance.array('nodejs', 'js'))), 38 | pg: { 39 | text: 'SELECT ("post"."tags" && ARRAY[$1, $2]) FROM "post"', 40 | string: 'SELECT ("post"."tags" && ARRAY[\'nodejs\', \'js\']) FROM "post"' 41 | }, 42 | params: ['nodejs', 'js'] 43 | }); 44 | 45 | Harness.test({ 46 | query: post.select(post.tags.slice(2, 3)), 47 | pg: { 48 | text: 'SELECT ("post"."tags")[$1:$2] FROM "post"', 49 | string: 'SELECT ("post"."tags")[2:3] FROM "post"' 50 | }, 51 | params: [2, 3] 52 | }); 53 | 54 | Harness.test({ 55 | query: post.select(post.tags.at(2)), 56 | pg: { 57 | text: 'SELECT ("post"."tags")[$1] FROM "post"', 58 | string: 'SELECT ("post"."tags")[2] FROM "post"' 59 | }, 60 | params: [2] 61 | }); 62 | 63 | Harness.test({ 64 | query: post.select(post.tags.overlap(instance.parameter(['nodejs', 'js']))), 65 | pg: { 66 | text: 'SELECT ("post"."tags" && $1) FROM "post"', 67 | string: 'SELECT ("post"."tags" && \'{"nodejs","js"}\') FROM "post"' 68 | }, 69 | params: [['nodejs', 'js']] 70 | }); 71 | 72 | // Array literals 73 | Harness.test({ 74 | query: post.select(instance.array(1, 2, 3)), 75 | pg: { 76 | text: 'SELECT ARRAY[$1, $2, $3] FROM "post"', 77 | string: 'SELECT ARRAY[1, 2, 3] FROM "post"' 78 | }, 79 | params: [1, 2, 3] 80 | }); 81 | 82 | Harness.test({ 83 | query: post.select(instance.array(1, 2, 3).slice(2, 3)), 84 | pg: { 85 | text: 'SELECT (ARRAY[$1, $2, $3])[$4:$5] FROM "post"', 86 | string: 'SELECT (ARRAY[1, 2, 3])[2:3] FROM "post"' 87 | }, 88 | params: [1, 2, 3, 2, 3] 89 | }); 90 | 91 | Harness.test({ 92 | query: post.select(instance.array(1, 2, 3).at(2)), 93 | pg: { 94 | text: 'SELECT (ARRAY[$1, $2, $3])[$4] FROM "post"', 95 | string: 'SELECT (ARRAY[1, 2, 3])[2] FROM "post"' 96 | }, 97 | params: [1, 2, 3, 2] 98 | }); 99 | -------------------------------------------------------------------------------- /test/dialects/as-of-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | import { Sql } from '../../dist/lib.js'; 3 | const post = Harness.definePostTable(); 4 | const user = Harness.defineUserTable(); 5 | const instance = new Sql('postgres'); 6 | 7 | Harness.test({ 8 | query: user 9 | .select(user.star()) 10 | .from(user) 11 | .asOf(instance.functions.FOLLOWER_READ_TIMESTAMP()), 12 | pg: { 13 | text: 'SELECT "user".* FROM "user" AS OF SYSTEM TIME FOLLOWER_READ_TIMESTAMP()', 14 | string: 'SELECT "user".* FROM "user" AS OF SYSTEM TIME FOLLOWER_READ_TIMESTAMP()' 15 | }, 16 | oracle: { 17 | text: 'SELECT "user".* FROM "user" AS OF TIMESTAMP FOLLOWER_READ_TIMESTAMP()', 18 | string: 'SELECT "user".* FROM "user" AS OF TIMESTAMP FOLLOWER_READ_TIMESTAMP()' 19 | } 20 | }); 21 | 22 | Harness.test({ 23 | query: user.select(user.star()).from([user, post]).asOf('\'-10s\''), 24 | pg: { 25 | text: 'SELECT "user".* FROM "user" , "post" AS OF SYSTEM TIME \'-10s\'', 26 | string: 'SELECT "user".* FROM "user" , "post" AS OF SYSTEM TIME \'-10s\'' 27 | }, 28 | oracle: { 29 | text: 'SELECT "user".* FROM "user" , "post" AS OF TIMESTAMP \'-10s\'', 30 | string: 'SELECT "user".* FROM "user" , "post" AS OF TIMESTAMP \'-10s\'' 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /test/dialects/binary-clause-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const customer = Harness.defineCustomerTable(); 3 | const customerAlias = Harness.defineCustomerAliasTable(); 4 | const post = Harness.definePostTable(); 5 | 6 | Harness.test({ 7 | query: customer.select(customer.name.plus(customer.age)), 8 | pg: { 9 | text: 'SELECT ("customer"."name" + "customer"."age") FROM "customer"', 10 | string: 'SELECT ("customer"."name" + "customer"."age") FROM "customer"' 11 | }, 12 | sqlite: { 13 | text: 'SELECT ("customer"."name" + "customer"."age") FROM "customer"', 14 | string: 'SELECT ("customer"."name" + "customer"."age") FROM "customer"' 15 | }, 16 | mysql: { 17 | text: 'SELECT (`customer`.`name` + `customer`.`age`) FROM `customer`', 18 | string: 'SELECT (`customer`.`name` + `customer`.`age`) FROM `customer`' 19 | }, 20 | mssql: { 21 | text: 'SELECT ([customer].[name] + [customer].[age]) FROM [customer]', 22 | string: 'SELECT ([customer].[name] + [customer].[age]) FROM [customer]' 23 | }, 24 | oracle: { 25 | text: 'SELECT ("customer"."name" + "customer"."age") FROM "customer"', 26 | string: 'SELECT ("customer"."name" + "customer"."age") FROM "customer"' 27 | }, 28 | params: [] 29 | }); 30 | 31 | Harness.test({ 32 | query: customerAlias.select(customerAlias.name_alias.plus(customerAlias.age_alias)), 33 | pg: { 34 | text: 'SELECT ("customer"."name" + "customer"."age") FROM "customer"', 35 | string: 'SELECT ("customer"."name" + "customer"."age") FROM "customer"' 36 | }, 37 | sqlite: { 38 | text: 'SELECT ("customer"."name" + "customer"."age") FROM "customer"', 39 | string: 'SELECT ("customer"."name" + "customer"."age") FROM "customer"' 40 | }, 41 | mysql: { 42 | text: 'SELECT (`customer`.`name` + `customer`.`age`) FROM `customer`', 43 | string: 'SELECT (`customer`.`name` + `customer`.`age`) FROM `customer`' 44 | }, 45 | mssql: { 46 | text: 'SELECT ([customer].[name] + [customer].[age]) FROM [customer]', 47 | string: 'SELECT ([customer].[name] + [customer].[age]) FROM [customer]' 48 | }, 49 | params: [] 50 | }); 51 | 52 | Harness.test({ 53 | query: post.select(post.content.plus('!')).where(post.userId.in(customer.subQuery().select(customer.id))), 54 | pg: { 55 | text: 'SELECT ("post"."content" + $1) FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer"))', 56 | string: 'SELECT ("post"."content" + \'!\') FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer"))' 57 | }, 58 | sqlite: { 59 | text: 'SELECT ("post"."content" + $1) FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer"))', 60 | string: 'SELECT ("post"."content" + \'!\') FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer"))' 61 | }, 62 | mysql: { 63 | text: 'SELECT (`post`.`content` + ?) FROM `post` WHERE (`post`.`userId` IN (SELECT `customer`.`id` FROM `customer`))', 64 | string: "SELECT (`post`.`content` + '!') FROM `post` WHERE (`post`.`userId` IN (SELECT `customer`.`id` FROM `customer`))" 65 | }, 66 | mssql: { 67 | text: 'SELECT ([post].[content] + @1) FROM [post] WHERE ([post].[userId] IN (SELECT [customer].[id] FROM [customer]))', 68 | string: "SELECT ([post].[content] + '!') FROM [post] WHERE ([post].[userId] IN (SELECT [customer].[id] FROM [customer]))" 69 | }, 70 | oracle: { 71 | text: 'SELECT ("post"."content" + :1) FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer"))', 72 | string: 'SELECT ("post"."content" + \'!\') FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer"))' 73 | }, 74 | params: ['!'] 75 | }); 76 | 77 | Harness.test({ 78 | query: post.select(post.id.plus(': ').plus(post.content)).where(post.userId.notIn(customer.subQuery().select(customer.id))), 79 | pg: { 80 | text: 81 | 'SELECT (("post"."id" + $1) + "post"."content") FROM "post" WHERE ("post"."userId" NOT IN (SELECT "customer"."id" FROM "customer"))', 82 | string: 83 | 'SELECT (("post"."id" + \': \') + "post"."content") FROM "post" WHERE ("post"."userId" NOT IN (SELECT "customer"."id" FROM "customer"))' 84 | }, 85 | sqlite: { 86 | text: 87 | 'SELECT (("post"."id" + $1) + "post"."content") FROM "post" WHERE ("post"."userId" NOT IN (SELECT "customer"."id" FROM "customer"))', 88 | string: 89 | 'SELECT (("post"."id" + \': \') + "post"."content") FROM "post" WHERE ("post"."userId" NOT IN (SELECT "customer"."id" FROM "customer"))' 90 | }, 91 | mysql: { 92 | text: 93 | 'SELECT ((`post`.`id` + ?) + `post`.`content`) FROM `post` WHERE (`post`.`userId` NOT IN (SELECT `customer`.`id` FROM `customer`))', 94 | string: 95 | "SELECT ((`post`.`id` + ': ') + `post`.`content`) FROM `post` WHERE (`post`.`userId` NOT IN (SELECT `customer`.`id` FROM `customer`))" 96 | }, 97 | mssql: { 98 | text: 99 | 'SELECT (([post].[id] + @1) + [post].[content]) FROM [post] WHERE ([post].[userId] NOT IN (SELECT [customer].[id] FROM [customer]))', 100 | string: 101 | "SELECT (([post].[id] + ': ') + [post].[content]) FROM [post] WHERE ([post].[userId] NOT IN (SELECT [customer].[id] FROM [customer]))" 102 | }, 103 | oracle: { 104 | text: 105 | 'SELECT (("post"."id" + :1) + "post"."content") FROM "post" WHERE ("post"."userId" NOT IN (SELECT "customer"."id" FROM "customer"))', 106 | string: 107 | 'SELECT (("post"."id" + \': \') + "post"."content") FROM "post" WHERE ("post"."userId" NOT IN (SELECT "customer"."id" FROM "customer"))' 108 | }, 109 | params: [': '] 110 | }); 111 | -------------------------------------------------------------------------------- /test/dialects/clause-ordering-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const post = Harness.definePostTable(); 3 | const user = Harness.defineUserTable(); 4 | 5 | // FROM - SELECT 6 | Harness.test({ 7 | query: user.from(user.join(post).on(user.id.equals(post.userId))).select(user.name, post.content), 8 | pg: { 9 | text: 'SELECT "user"."name", "post"."content" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId")', 10 | string: 'SELECT "user"."name", "post"."content" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId")' 11 | }, 12 | sqlite: { 13 | text: 'SELECT "user"."name", "post"."content" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId")', 14 | string: 'SELECT "user"."name", "post"."content" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId")' 15 | }, 16 | mysql: { 17 | text: 'SELECT `user`.`name`, `post`.`content` FROM `user` INNER JOIN `post` ON (`user`.`id` = `post`.`userId`)', 18 | string: 'SELECT `user`.`name`, `post`.`content` FROM `user` INNER JOIN `post` ON (`user`.`id` = `post`.`userId`)' 19 | }, 20 | mssql: { 21 | text: 'SELECT [user].[name], [post].[content] FROM [user] INNER JOIN [post] ON ([user].[id] = [post].[userId])', 22 | string: 'SELECT [user].[name], [post].[content] FROM [user] INNER JOIN [post] ON ([user].[id] = [post].[userId])' 23 | }, 24 | oracle: { 25 | text: 'SELECT "user"."name", "post"."content" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId")', 26 | string: 'SELECT "user"."name", "post"."content" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId")' 27 | }, 28 | params: [] 29 | }); 30 | 31 | // WHERE - FROM - SELECT 32 | Harness.test({ 33 | query: user 34 | .where({ 35 | name: '' 36 | }) 37 | .from(user) 38 | .select(user.id), 39 | pg: { 40 | text: 'SELECT "user"."id" FROM "user" WHERE ("user"."name" = $1)', 41 | string: 'SELECT "user"."id" FROM "user" WHERE ("user"."name" = \'\')' 42 | }, 43 | sqlite: { 44 | text: 'SELECT "user"."id" FROM "user" WHERE ("user"."name" = $1)', 45 | string: 'SELECT "user"."id" FROM "user" WHERE ("user"."name" = \'\')' 46 | }, 47 | mysql: { 48 | text: 'SELECT `user`.`id` FROM `user` WHERE (`user`.`name` = ?)', 49 | string: "SELECT `user`.`id` FROM `user` WHERE (`user`.`name` = '')" 50 | }, 51 | mssql: { 52 | text: 'SELECT [user].[id] FROM [user] WHERE ([user].[name] = @1)', 53 | string: "SELECT [user].[id] FROM [user] WHERE ([user].[name] = '')" 54 | }, 55 | oracle: { 56 | text: 'SELECT "user"."id" FROM "user" WHERE ("user"."name" = :1)', 57 | string: 'SELECT "user"."id" FROM "user" WHERE ("user"."name" = \'\')' 58 | }, 59 | params: [''] 60 | }); 61 | 62 | // SELECT - FROM - WHERE 63 | Harness.test({ 64 | query: user 65 | .select(user.name, post.content) 66 | .from(user.join(post).on(user.id.equals(post.userId))) 67 | .where({ 68 | name: '' 69 | }), 70 | pg: { 71 | text: 72 | 'SELECT "user"."name", "post"."content" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId") WHERE ("user"."name" = $1)', 73 | string: 74 | 'SELECT "user"."name", "post"."content" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId") WHERE ("user"."name" = \'\')' 75 | }, 76 | sqlite: { 77 | text: 78 | 'SELECT "user"."name", "post"."content" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId") WHERE ("user"."name" = $1)', 79 | string: 80 | 'SELECT "user"."name", "post"."content" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId") WHERE ("user"."name" = \'\')' 81 | }, 82 | mysql: { 83 | text: 84 | 'SELECT `user`.`name`, `post`.`content` FROM `user` INNER JOIN `post` ON (`user`.`id` = `post`.`userId`) WHERE (`user`.`name` = ?)', 85 | string: 86 | "SELECT `user`.`name`, `post`.`content` FROM `user` INNER JOIN `post` ON (`user`.`id` = `post`.`userId`) WHERE (`user`.`name` = '')" 87 | }, 88 | mssql: { 89 | text: 90 | 'SELECT [user].[name], [post].[content] FROM [user] INNER JOIN [post] ON ([user].[id] = [post].[userId]) WHERE ([user].[name] = @1)', 91 | string: 92 | "SELECT [user].[name], [post].[content] FROM [user] INNER JOIN [post] ON ([user].[id] = [post].[userId]) WHERE ([user].[name] = '')" 93 | }, 94 | oracle: { 95 | text: 96 | 'SELECT "user"."name", "post"."content" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId") WHERE ("user"."name" = :1)', 97 | string: 98 | 'SELECT "user"."name", "post"."content" FROM "user" INNER JOIN "post" ON ("user"."id" = "post"."userId") WHERE ("user"."name" = \'\')' 99 | }, 100 | params: [''] 101 | }); 102 | 103 | // SELECT - FROM - WHERE 104 | Harness.test({ 105 | query: user 106 | .select(user.id) 107 | .from(user) 108 | .where({ 109 | name: '' 110 | }), 111 | pg: { 112 | text: 'SELECT "user"."id" FROM "user" WHERE ("user"."name" = $1)', 113 | string: 'SELECT "user"."id" FROM "user" WHERE ("user"."name" = \'\')' 114 | }, 115 | sqlite: { 116 | text: 'SELECT "user"."id" FROM "user" WHERE ("user"."name" = $1)', 117 | string: 'SELECT "user"."id" FROM "user" WHERE ("user"."name" = \'\')' 118 | }, 119 | mysql: { 120 | text: 'SELECT `user`.`id` FROM `user` WHERE (`user`.`name` = ?)', 121 | string: "SELECT `user`.`id` FROM `user` WHERE (`user`.`name` = '')" 122 | }, 123 | mssql: { 124 | text: 'SELECT [user].[id] FROM [user] WHERE ([user].[name] = @1)', 125 | string: "SELECT [user].[id] FROM [user] WHERE ([user].[name] = '')" 126 | }, 127 | oracle: { 128 | text: 'SELECT "user"."id" FROM "user" WHERE ("user"."name" = :1)', 129 | string: 'SELECT "user"."id" FROM "user" WHERE ("user"."name" = \'\')' 130 | }, 131 | params: [''] 132 | }); 133 | -------------------------------------------------------------------------------- /test/dialects/create-view-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const user = Harness.defineUserTable(); 3 | 4 | // simple view create 5 | Harness.test({ 6 | query: user.select(user.star()).createView('allUsersView'), 7 | pg: { 8 | text: '(CREATE VIEW "allUsersView" AS SELECT "user".* FROM "user")', 9 | string: '(CREATE VIEW "allUsersView" AS SELECT "user".* FROM "user")' 10 | }, 11 | sqlite: { 12 | text: '(CREATE VIEW "allUsersView" AS SELECT "user".* FROM "user")', 13 | string: '(CREATE VIEW "allUsersView" AS SELECT "user".* FROM "user")' 14 | }, 15 | mysql: { 16 | text: '(CREATE VIEW `allUsersView` AS SELECT `user`.* FROM `user`)', 17 | string: '(CREATE VIEW `allUsersView` AS SELECT `user`.* FROM `user`)' 18 | }, 19 | mssql: { 20 | text: '(CREATE VIEW [allUsersView] AS SELECT [user].* FROM [user])', 21 | string: '(CREATE VIEW [allUsersView] AS SELECT [user].* FROM [user])' 22 | }, 23 | oracle: { 24 | text: '(CREATE VIEW "allUsersView" AS SELECT "user".* FROM "user")', 25 | string: '(CREATE VIEW "allUsersView" AS SELECT "user".* FROM "user")' 26 | } 27 | }); 28 | 29 | // create view with parameters 30 | Harness.test({ 31 | query: user 32 | .select(user.star()) 33 | .where(user.id.equals(1)) 34 | .createView('oneUserView'), 35 | pg: { 36 | text: '(CREATE VIEW "oneUserView" AS SELECT "user".* FROM "user" WHERE ("user"."id" = 1))', 37 | string: '(CREATE VIEW "oneUserView" AS SELECT "user".* FROM "user" WHERE ("user"."id" = 1))' 38 | }, 39 | sqlite: { 40 | text: '(CREATE VIEW "oneUserView" AS SELECT "user".* FROM "user" WHERE ("user"."id" = 1))', 41 | string: '(CREATE VIEW "oneUserView" AS SELECT "user".* FROM "user" WHERE ("user"."id" = 1))' 42 | }, 43 | mysql: { 44 | text: '(CREATE VIEW `oneUserView` AS SELECT `user`.* FROM `user` WHERE (`user`.`id` = 1))', 45 | string: '(CREATE VIEW `oneUserView` AS SELECT `user`.* FROM `user` WHERE (`user`.`id` = 1))' 46 | }, 47 | mssql: { 48 | text: '(CREATE VIEW [oneUserView] AS SELECT [user].* FROM [user] WHERE ([user].[id] = 1))', 49 | string: '(CREATE VIEW [oneUserView] AS SELECT [user].* FROM [user] WHERE ([user].[id] = 1))' 50 | }, 51 | oracle: { 52 | text: '(CREATE VIEW "oneUserView" AS SELECT "user".* FROM "user" WHERE ("user"."id" = 1))', 53 | string: '(CREATE VIEW "oneUserView" AS SELECT "user".* FROM "user" WHERE ("user"."id" = 1))' 54 | } 55 | }); 56 | 57 | // Tests error raised for non-SELECT create view attempts 58 | Harness.test({ 59 | query: user 60 | .delete() 61 | .where(user.id.equals(1)) 62 | .createView('oneUserView'), 63 | pg: { 64 | text: 'Create View requires a Select.', 65 | throws: true 66 | }, 67 | sqlite: { 68 | text: 'Create View requires a Select.', 69 | throws: true 70 | }, 71 | mysql: { 72 | text: 'Create View requires a Select.', 73 | throws: true 74 | }, 75 | mssql: { 76 | text: 'Create View requires a Select.', 77 | throws: true 78 | }, 79 | oracle: { 80 | text: 'Create View requires a Select.', 81 | throws: true 82 | }, 83 | params: [] 84 | }); 85 | -------------------------------------------------------------------------------- /test/dialects/delete-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const post = Harness.definePostTable(); 3 | const user = Harness.defineUserTable(); 4 | 5 | Harness.test({ 6 | query: post.delete().where(post.content.equals("hello's world")), 7 | pg: { 8 | text: 'DELETE FROM "post" WHERE ("post"."content" = $1)', 9 | string: 'DELETE FROM "post" WHERE ("post"."content" = \'hello\'\'s world\')' 10 | }, 11 | sqlite: { 12 | text: 'DELETE FROM "post" WHERE ("post"."content" = $1)', 13 | string: 'DELETE FROM "post" WHERE ("post"."content" = \'hello\'\'s world\')' 14 | }, 15 | mysql: { 16 | text: 'DELETE FROM `post` WHERE (`post`.`content` = ?)', 17 | string: "DELETE FROM `post` WHERE (`post`.`content` = 'hello''s world')" 18 | }, 19 | mssql: { 20 | text: 'DELETE FROM [post] WHERE ([post].[content] = @1)', 21 | string: "DELETE FROM [post] WHERE ([post].[content] = 'hello''s world')" 22 | }, 23 | oracle: { 24 | text: 'DELETE FROM "post" WHERE ("post"."content" = :1)', 25 | string: 'DELETE FROM "post" WHERE ("post"."content" = \'hello\'\'s world\')' 26 | }, 27 | params: ["hello's world"] 28 | }); 29 | 30 | Harness.test({ 31 | query: post.delete(post).from(post), 32 | pg: { 33 | text: 'DELETE "post" FROM "post"', 34 | string: 'DELETE "post" FROM "post"' 35 | }, 36 | sqlite: { 37 | text: 'DELETE "post" FROM "post"', 38 | string: 'DELETE "post" FROM "post"' 39 | }, 40 | mysql: { 41 | text: 'DELETE `post` FROM `post`', 42 | string: 'DELETE `post` FROM `post`' 43 | }, 44 | params: [] 45 | }); 46 | 47 | Harness.test({ 48 | query: post.delete([post, post]).from(post), 49 | pg: { 50 | text: 'DELETE "post", "post" FROM "post"', 51 | string: 'DELETE "post", "post" FROM "post"' 52 | }, 53 | sqlite: { 54 | text: 'DELETE "post", "post" FROM "post"', 55 | string: 'DELETE "post", "post" FROM "post"' 56 | }, 57 | mysql: { 58 | text: 'DELETE `post`, `post` FROM `post`', 59 | string: 'DELETE `post`, `post` FROM `post`' 60 | }, 61 | params: [] 62 | }); 63 | 64 | Harness.test({ 65 | query: user 66 | .delete(user) 67 | .from(user.join(post).on(post.userId.equals(user.id))) 68 | .where(post.content.equals('foo')), 69 | pg: { 70 | text: 'DELETE "user" FROM "user" INNER JOIN "post" ON ("post"."userId" = "user"."id") WHERE ("post"."content" = $1)', 71 | string: 'DELETE "user" FROM "user" INNER JOIN "post" ON ("post"."userId" = "user"."id") WHERE ("post"."content" = \'foo\')' 72 | }, 73 | sqlite: { 74 | text: 'DELETE "user" FROM "user" INNER JOIN "post" ON ("post"."userId" = "user"."id") WHERE ("post"."content" = $1)', 75 | string: 'DELETE "user" FROM "user" INNER JOIN "post" ON ("post"."userId" = "user"."id") WHERE ("post"."content" = \'foo\')' 76 | }, 77 | mysql: { 78 | text: 'DELETE `user` FROM `user` INNER JOIN `post` ON (`post`.`userId` = `user`.`id`) WHERE (`post`.`content` = ?)', 79 | string: "DELETE `user` FROM `user` INNER JOIN `post` ON (`post`.`userId` = `user`.`id`) WHERE (`post`.`content` = 'foo')" 80 | }, 81 | oracle: { 82 | text: 'DELETE "user" FROM "user" INNER JOIN "post" ON ("post"."userId" = "user"."id") WHERE ("post"."content" = :1)', 83 | string: 'DELETE "user" FROM "user" INNER JOIN "post" ON ("post"."userId" = "user"."id") WHERE ("post"."content" = \'foo\')' 84 | }, 85 | params: ['foo'] 86 | }); 87 | 88 | Harness.test({ 89 | query: post.delete().where({ 90 | content: '' 91 | }), 92 | pg: { 93 | text: 'DELETE FROM "post" WHERE ("post"."content" = $1)', 94 | string: 'DELETE FROM "post" WHERE ("post"."content" = \'\')' 95 | }, 96 | sqlite: { 97 | text: 'DELETE FROM "post" WHERE ("post"."content" = $1)', 98 | string: 'DELETE FROM "post" WHERE ("post"."content" = \'\')' 99 | }, 100 | mysql: { 101 | text: 'DELETE FROM `post` WHERE (`post`.`content` = ?)', 102 | string: "DELETE FROM `post` WHERE (`post`.`content` = '')" 103 | }, 104 | mssql: { 105 | text: 'DELETE FROM [post] WHERE ([post].[content] = @1)', 106 | string: "DELETE FROM [post] WHERE ([post].[content] = '')" 107 | }, 108 | oracle: { 109 | text: 'DELETE FROM "post" WHERE ("post"."content" = :1)', 110 | string: 'DELETE FROM "post" WHERE ("post"."content" = \'\')' 111 | }, 112 | params: [''] 113 | }); 114 | 115 | Harness.test({ 116 | query: post.delete({ 117 | content: '' 118 | }), 119 | pg: { 120 | text: 'DELETE FROM "post" WHERE ("post"."content" = $1)', 121 | string: 'DELETE FROM "post" WHERE ("post"."content" = \'\')' 122 | }, 123 | sqlite: { 124 | text: 'DELETE FROM "post" WHERE ("post"."content" = $1)', 125 | string: 'DELETE FROM "post" WHERE ("post"."content" = \'\')' 126 | }, 127 | mysql: { 128 | text: 'DELETE FROM `post` WHERE (`post`.`content` = ?)', 129 | string: "DELETE FROM `post` WHERE (`post`.`content` = '')" 130 | }, 131 | mssql: { 132 | text: 'DELETE FROM [post] WHERE ([post].[content] = @1)', 133 | string: "DELETE FROM [post] WHERE ([post].[content] = '')" 134 | }, 135 | oracle: { 136 | text: 'DELETE FROM "post" WHERE ("post"."content" = :1)', 137 | string: 'DELETE FROM "post" WHERE ("post"."content" = \'\')' 138 | }, 139 | params: [''] 140 | }); 141 | 142 | Harness.test({ 143 | query: post 144 | .delete({ 145 | content: '' 146 | }) 147 | .or(post.content.isNull()), 148 | pg: { 149 | text: 'DELETE FROM "post" WHERE (("post"."content" = $1) OR ("post"."content" IS NULL))', 150 | string: 'DELETE FROM "post" WHERE (("post"."content" = \'\') OR ("post"."content" IS NULL))' 151 | }, 152 | sqlite: { 153 | text: 'DELETE FROM "post" WHERE (("post"."content" = $1) OR ("post"."content" IS NULL))', 154 | string: 'DELETE FROM "post" WHERE (("post"."content" = \'\') OR ("post"."content" IS NULL))' 155 | }, 156 | mysql: { 157 | text: 'DELETE FROM `post` WHERE ((`post`.`content` = ?) OR (`post`.`content` IS NULL))', 158 | string: "DELETE FROM `post` WHERE ((`post`.`content` = '') OR (`post`.`content` IS NULL))" 159 | }, 160 | mssql: { 161 | text: 'DELETE FROM [post] WHERE (([post].[content] = @1) OR ([post].[content] IS NULL))', 162 | string: "DELETE FROM [post] WHERE (([post].[content] = '') OR ([post].[content] IS NULL))" 163 | }, 164 | oracle: { 165 | text: 'DELETE FROM "post" WHERE (("post"."content" = :1) OR ("post"."content" IS NULL))', 166 | string: 'DELETE FROM "post" WHERE (("post"."content" = \'\') OR ("post"."content" IS NULL))' 167 | }, 168 | params: [''] 169 | }); 170 | -------------------------------------------------------------------------------- /test/dialects/distinct-on-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const user = Harness.defineUserTable(); 3 | const customerAlias = Harness.defineCustomerAliasTable(); 4 | 5 | Harness.test({ 6 | query: user.select().distinctOn(user.id), 7 | pg: { 8 | text: 'SELECT DISTINCT ON("user"."id") "user".* FROM "user"', 9 | string: 'SELECT DISTINCT ON("user"."id") "user".* FROM "user"' 10 | }, 11 | params: [] 12 | }); 13 | 14 | Harness.test({ 15 | query: customerAlias.select().distinctOn(customerAlias.id_alias), 16 | pg: { 17 | text: 'SELECT DISTINCT ON("customer"."id") "customer"."id" AS "id_alias", "customer"."name" AS "name_alias", "customer"."age" AS "age_alias", "customer"."income" AS "income_alias", "customer"."metadata" AS "metadata_alias" FROM "customer"', 18 | string: 'SELECT DISTINCT ON("customer"."id") "customer"."id" AS "id_alias", "customer"."name" AS "name_alias", "customer"."age" AS "age_alias", "customer"."income" AS "income_alias", "customer"."metadata" AS "metadata_alias" FROM "customer"' 19 | }, 20 | params: [] 21 | }); 22 | 23 | Harness.test({ 24 | query: customerAlias.select(customerAlias.id_alias, customerAlias.name_alias).distinctOn(customerAlias.id_alias), 25 | pg: { 26 | text: 'SELECT DISTINCT ON("customer"."id") "customer"."id" AS "id_alias", "customer"."name" AS "name_alias" FROM "customer"', 27 | string: 'SELECT DISTINCT ON("customer"."id") "customer"."id" AS "id_alias", "customer"."name" AS "name_alias" FROM "customer"' 28 | }, 29 | params: [] 30 | }); 31 | -------------------------------------------------------------------------------- /test/dialects/distinct-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const user = Harness.defineUserTable(); 3 | 4 | Harness.test({ 5 | query: user.select(user.id.distinct()), 6 | pg: { 7 | text: 'SELECT DISTINCT("user"."id") FROM "user"', 8 | string: 'SELECT DISTINCT("user"."id") FROM "user"' 9 | }, 10 | sqlite: { 11 | text: 'SELECT DISTINCT("user"."id") FROM "user"', 12 | string: 'SELECT DISTINCT("user"."id") FROM "user"' 13 | }, 14 | mysql: { 15 | text: 'SELECT DISTINCT(`user`.`id`) FROM `user`', 16 | string: 'SELECT DISTINCT(`user`.`id`) FROM `user`' 17 | }, 18 | mssql: { 19 | text: 'SELECT DISTINCT([user].[id]) FROM [user]', 20 | string: 'SELECT DISTINCT([user].[id]) FROM [user]' 21 | }, 22 | oracle: { 23 | text: 'SELECT DISTINCT("user"."id") FROM "user"', 24 | string: 'SELECT DISTINCT("user"."id") FROM "user"' 25 | }, 26 | params: [] 27 | }); 28 | 29 | Harness.test({ 30 | query: user.select( 31 | user.id 32 | .count() 33 | .distinct() 34 | .as('count') 35 | ), 36 | pg: { 37 | text: 'SELECT COUNT(DISTINCT("user"."id")) AS "count" FROM "user"', 38 | string: 'SELECT COUNT(DISTINCT("user"."id")) AS "count" FROM "user"' 39 | }, 40 | sqlite: { 41 | text: 'SELECT COUNT(DISTINCT("user"."id")) AS "count" FROM "user"', 42 | string: 'SELECT COUNT(DISTINCT("user"."id")) AS "count" FROM "user"' 43 | }, 44 | mysql: { 45 | text: 'SELECT COUNT(DISTINCT(`user`.`id`)) AS `count` FROM `user`', 46 | string: 'SELECT COUNT(DISTINCT(`user`.`id`)) AS `count` FROM `user`' 47 | }, 48 | mssql: { 49 | text: 'SELECT COUNT(DISTINCT([user].[id])) AS [count] FROM [user]', 50 | string: 'SELECT COUNT(DISTINCT([user].[id])) AS [count] FROM [user]' 51 | }, 52 | oracle: { 53 | text: 'SELECT COUNT(DISTINCT("user"."id")) "count" FROM "user"', 54 | string: 'SELECT COUNT(DISTINCT("user"."id")) "count" FROM "user"' 55 | }, 56 | params: [] 57 | }); 58 | 59 | // BELOW HERE TEST DISTINCT ON THE ENTIRE RESULTS SET, NOT JUST ONE COLUMN 60 | 61 | Harness.test({ 62 | query: user.select().distinct(), 63 | pg: { 64 | text: 'SELECT DISTINCT "user".* FROM "user"', 65 | string: 'SELECT DISTINCT "user".* FROM "user"' 66 | }, 67 | sqlite: { 68 | text: 'SELECT DISTINCT "user".* FROM "user"', 69 | string: 'SELECT DISTINCT "user".* FROM "user"' 70 | }, 71 | mysql: { 72 | text: 'SELECT DISTINCT `user`.* FROM `user`', 73 | string: 'SELECT DISTINCT `user`.* FROM `user`' 74 | }, 75 | mssql: { 76 | text: 'SELECT DISTINCT [user].* FROM [user]', 77 | string: 'SELECT DISTINCT [user].* FROM [user]' 78 | }, 79 | oracle: { 80 | text: 'SELECT DISTINCT "user".* FROM "user"', 81 | string: 'SELECT DISTINCT "user".* FROM "user"' 82 | }, 83 | params: [] 84 | }); 85 | 86 | Harness.test({ 87 | query: user.select(user.id).distinct(), 88 | pg: { 89 | text: 'SELECT DISTINCT "user"."id" FROM "user"', 90 | string: 'SELECT DISTINCT "user"."id" FROM "user"' 91 | }, 92 | sqlite: { 93 | text: 'SELECT DISTINCT "user"."id" FROM "user"', 94 | string: 'SELECT DISTINCT "user"."id" FROM "user"' 95 | }, 96 | mysql: { 97 | text: 'SELECT DISTINCT `user`.`id` FROM `user`', 98 | string: 'SELECT DISTINCT `user`.`id` FROM `user`' 99 | }, 100 | mssql: { 101 | text: 'SELECT DISTINCT [user].[id] FROM [user]', 102 | string: 'SELECT DISTINCT [user].[id] FROM [user]' 103 | }, 104 | oracle: { 105 | text: 'SELECT DISTINCT "user"."id" FROM "user"', 106 | string: 'SELECT DISTINCT "user"."id" FROM "user"' 107 | }, 108 | params: [] 109 | }); 110 | 111 | Harness.test({ 112 | query: user.select(user.id, user.name).distinct(), 113 | pg: { 114 | text: 'SELECT DISTINCT "user"."id", "user"."name" FROM "user"', 115 | string: 'SELECT DISTINCT "user"."id", "user"."name" FROM "user"' 116 | }, 117 | sqlite: { 118 | text: 'SELECT DISTINCT "user"."id", "user"."name" FROM "user"', 119 | string: 'SELECT DISTINCT "user"."id", "user"."name" FROM "user"' 120 | }, 121 | mysql: { 122 | text: 'SELECT DISTINCT `user`.`id`, `user`.`name` FROM `user`', 123 | string: 'SELECT DISTINCT `user`.`id`, `user`.`name` FROM `user`' 124 | }, 125 | mssql: { 126 | text: 'SELECT DISTINCT [user].[id], [user].[name] FROM [user]', 127 | string: 'SELECT DISTINCT [user].[id], [user].[name] FROM [user]' 128 | }, 129 | oracle: { 130 | text: 'SELECT DISTINCT "user"."id", "user"."name" FROM "user"', 131 | string: 'SELECT DISTINCT "user"."id", "user"."name" FROM "user"' 132 | }, 133 | params: [] 134 | }); 135 | -------------------------------------------------------------------------------- /test/dialects/drop-table-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const post = Harness.definePostTable(); 3 | 4 | Harness.test({ 5 | query: post.drop(), 6 | pg: { 7 | text: 'DROP TABLE "post"', 8 | string: 'DROP TABLE "post"' 9 | }, 10 | sqlite: { 11 | text: 'DROP TABLE "post"', 12 | string: 'DROP TABLE "post"' 13 | }, 14 | mysql: { 15 | text: 'DROP TABLE `post`', 16 | string: 'DROP TABLE `post`' 17 | }, 18 | mssql: { 19 | text: 'DROP TABLE [post]', 20 | string: 'DROP TABLE [post]' 21 | }, 22 | oracle: { 23 | text: 'DROP TABLE "post"', 24 | string: 'DROP TABLE "post"' 25 | }, 26 | params: [] 27 | }); 28 | 29 | Harness.test({ 30 | query: post.drop().ifExists(), 31 | pg: { 32 | text: 'DROP TABLE IF EXISTS "post"', 33 | string: 'DROP TABLE IF EXISTS "post"' 34 | }, 35 | sqlite: { 36 | text: 'DROP TABLE IF EXISTS "post"', 37 | string: 'DROP TABLE IF EXISTS "post"' 38 | }, 39 | mysql: { 40 | text: 'DROP TABLE IF EXISTS `post`', 41 | string: 'DROP TABLE IF EXISTS `post`' 42 | }, 43 | mssql: { 44 | text: 'IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = [post]) BEGIN DROP TABLE [post] END', 45 | string: 'IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = [post]) BEGIN DROP TABLE [post] END' 46 | }, 47 | oracle: { 48 | text: 'BEGIN EXECUTE IMMEDIATE \'DROP TABLE "post"\'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;', 49 | string: 'BEGIN EXECUTE IMMEDIATE \'DROP TABLE "post"\'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;' 50 | }, 51 | params: [] 52 | }); 53 | 54 | Harness.test({ 55 | query: post.drop().cascade(), 56 | pg: { 57 | text: 'DROP TABLE "post" CASCADE', 58 | string: 'DROP TABLE "post" CASCADE' 59 | }, 60 | sqlite: { 61 | text: 'Sqlite do not support CASCADE in DROP TABLE', 62 | throws: true 63 | }, 64 | mysql: { 65 | text: 'DROP TABLE `post` CASCADE', 66 | string: 'DROP TABLE `post` CASCADE' 67 | }, 68 | oracle: { 69 | text: 'DROP TABLE "post" CASCADE CONSTRAINTS', 70 | string: 'DROP TABLE "post" CASCADE CONSTRAINTS' 71 | }, 72 | params: [] 73 | }); 74 | 75 | Harness.test({ 76 | query: post.drop().restrict(), 77 | pg: { 78 | text: 'DROP TABLE "post" RESTRICT', 79 | string: 'DROP TABLE "post" RESTRICT' 80 | }, 81 | sqlite: { 82 | text: 'Sqlite do not support RESTRICT in DROP TABLE', 83 | throws: true 84 | }, 85 | mysql: { 86 | text: 'DROP TABLE `post` RESTRICT', 87 | string: 'DROP TABLE `post` RESTRICT' 88 | }, 89 | oracle: { 90 | text: 'Oracle do not support RESTRICT in DROP TABLE', 91 | throws: true 92 | }, 93 | params: [] 94 | }); 95 | -------------------------------------------------------------------------------- /test/dialects/for-share-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const post = Harness.definePostTable(); 3 | const user = Harness.defineUserTable(); 4 | 5 | Harness.test({ 6 | query: post.select(post.star()).forShare(), 7 | pg: { 8 | text: 'SELECT "post".* FROM "post" FOR SHARE', 9 | string: 'SELECT "post".* FROM "post" FOR SHARE' 10 | }, 11 | params: [] 12 | }); 13 | 14 | Harness.test({ 15 | query: post 16 | .select(post.star()) 17 | .from(post.join(user).on(user.id.equals(post.userId))) 18 | .where(post.content.equals('foo')) 19 | .forShare(), 20 | pg: { 21 | text: 'SELECT "post".* FROM "post" INNER JOIN "user" ON ("user"."id" = "post"."userId") WHERE ("post"."content" = $1) FOR SHARE', 22 | string: 23 | 'SELECT "post".* FROM "post" INNER JOIN "user" ON ("user"."id" = "post"."userId") WHERE ("post"."content" = \'foo\') FOR SHARE' 24 | }, 25 | params: ['foo'] 26 | }); 27 | -------------------------------------------------------------------------------- /test/dialects/for-update-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const post = Harness.definePostTable(); 3 | const user = Harness.defineUserTable(); 4 | 5 | Harness.test({ 6 | query: post.select(post.star()).forUpdate(), 7 | pg: { 8 | text: 'SELECT "post".* FROM "post" FOR UPDATE', 9 | string: 'SELECT "post".* FROM "post" FOR UPDATE' 10 | }, 11 | mysql: { 12 | text: 'SELECT `post`.* FROM `post` FOR UPDATE', 13 | string: 'SELECT `post`.* FROM `post` FOR UPDATE' 14 | }, 15 | params: [] 16 | }); 17 | 18 | Harness.test({ 19 | query: post 20 | .select(post.star()) 21 | .from(post.join(user).on(user.id.equals(post.userId))) 22 | .where(post.content.equals('foo')) 23 | .forUpdate(), 24 | pg: { 25 | text: 'SELECT "post".* FROM "post" INNER JOIN "user" ON ("user"."id" = "post"."userId") WHERE ("post"."content" = $1) FOR UPDATE', 26 | string: 27 | 'SELECT "post".* FROM "post" INNER JOIN "user" ON ("user"."id" = "post"."userId") WHERE ("post"."content" = \'foo\') FOR UPDATE' 28 | }, 29 | mysql: { 30 | text: 'SELECT `post`.* FROM `post` INNER JOIN `user` ON (`user`.`id` = `post`.`userId`) WHERE (`post`.`content` = ?) FOR UPDATE', 31 | string: 32 | "SELECT `post`.* FROM `post` INNER JOIN `user` ON (`user`.`id` = `post`.`userId`) WHERE (`post`.`content` = 'foo') FOR UPDATE" 33 | }, 34 | params: ['foo'] 35 | }); 36 | -------------------------------------------------------------------------------- /test/dialects/from-clause-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const post = Harness.definePostTable(); 3 | const user = Harness.defineUserTable(); 4 | 5 | Harness.test({ 6 | query: user 7 | .select(user.star()) 8 | .from(user) 9 | .from(post), 10 | pg: { 11 | text: 'SELECT "user".* FROM "user" , "post"', 12 | string: 'SELECT "user".* FROM "user" , "post"' 13 | }, 14 | sqlite: { 15 | text: 'SELECT "user".* FROM "user" , "post"', 16 | string: 'SELECT "user".* FROM "user" , "post"' 17 | }, 18 | mysql: { 19 | text: 'SELECT `user`.* FROM `user` , `post`', 20 | string: 'SELECT `user`.* FROM `user` , `post`' 21 | }, 22 | mssql: { 23 | text: 'SELECT [user].* FROM [user] , [post]', 24 | string: 'SELECT [user].* FROM [user] , [post]' 25 | }, 26 | oracle: { 27 | text: 'SELECT "user".* FROM "user" , "post"', 28 | string: 'SELECT "user".* FROM "user" , "post"' 29 | } 30 | }); 31 | 32 | Harness.test({ 33 | query: user 34 | .select(user.star(), post.star()) 35 | .from(user) 36 | .from(post), 37 | pg: { 38 | text: 'SELECT "user".*, "post".* FROM "user" , "post"', 39 | string: 'SELECT "user".*, "post".* FROM "user" , "post"' 40 | }, 41 | sqlite: { 42 | text: 'SELECT "user".*, "post".* FROM "user" , "post"', 43 | string: 'SELECT "user".*, "post".* FROM "user" , "post"' 44 | }, 45 | mysql: { 46 | text: 'SELECT `user`.*, `post`.* FROM `user` , `post`', 47 | string: 'SELECT `user`.*, `post`.* FROM `user` , `post`' 48 | }, 49 | mssql: { 50 | text: 'SELECT [user].*, [post].* FROM [user] , [post]', 51 | string: 'SELECT [user].*, [post].* FROM [user] , [post]' 52 | }, 53 | oracle: { 54 | text: 'SELECT "user".*, "post".* FROM "user" , "post"', 55 | string: 'SELECT "user".*, "post".* FROM "user" , "post"' 56 | } 57 | }); 58 | 59 | Harness.test({ 60 | query: user.select(user.star()).from(user, post), 61 | pg: { 62 | text: 'SELECT "user".* FROM "user" , "post"', 63 | string: 'SELECT "user".* FROM "user" , "post"' 64 | }, 65 | sqlite: { 66 | text: 'SELECT "user".* FROM "user" , "post"', 67 | string: 'SELECT "user".* FROM "user" , "post"' 68 | }, 69 | mysql: { 70 | text: 'SELECT `user`.* FROM `user` , `post`', 71 | string: 'SELECT `user`.* FROM `user` , `post`' 72 | }, 73 | oracle: { 74 | text: 'SELECT "user".* FROM "user" , "post"', 75 | string: 'SELECT "user".* FROM "user" , "post"' 76 | } 77 | }); 78 | 79 | Harness.test({ 80 | query: user.select(user.star()).from([user, post]), 81 | pg: { 82 | text: 'SELECT "user".* FROM "user" , "post"', 83 | string: 'SELECT "user".* FROM "user" , "post"' 84 | }, 85 | sqlite: { 86 | text: 'SELECT "user".* FROM "user" , "post"', 87 | string: 'SELECT "user".* FROM "user" , "post"' 88 | }, 89 | mysql: { 90 | text: 'SELECT `user`.* FROM `user` , `post`', 91 | string: 'SELECT `user`.* FROM `user` , `post`' 92 | }, 93 | oracle: { 94 | text: 'SELECT "user".* FROM "user" , "post"', 95 | string: 'SELECT "user".* FROM "user" , "post"' 96 | } 97 | }); 98 | -------------------------------------------------------------------------------- /test/dialects/function-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | import { Sql } from '../../dist/lib.js'; 3 | const post = Harness.definePostTable(); 4 | const instance = new Sql('postgres'); 5 | 6 | Harness.test({ 7 | query: post.select(instance.functions.LENGTH(post.content)), 8 | pg: { 9 | text: 'SELECT LENGTH("post"."content") FROM "post"', 10 | string: 'SELECT LENGTH("post"."content") FROM "post"' 11 | }, 12 | sqlite: { 13 | text: 'SELECT LENGTH("post"."content") FROM "post"', 14 | string: 'SELECT LENGTH("post"."content") FROM "post"' 15 | }, 16 | mysql: { 17 | text: 'SELECT LENGTH(`post`.`content`) FROM `post`', 18 | string: 'SELECT LENGTH(`post`.`content`) FROM `post`' 19 | }, 20 | mssql: { 21 | text: 'SELECT LEN([post].[content]) FROM [post]', 22 | string: 'SELECT LEN([post].[content]) FROM [post]' 23 | }, 24 | oracle: { 25 | text: 'SELECT LENGTH("post"."content") FROM "post"', 26 | string: 'SELECT LENGTH("post"."content") FROM "post"' 27 | }, 28 | params: [] 29 | }); 30 | 31 | Harness.test({ 32 | query: post.select(instance.functions.LEFT(post.content, 4)), 33 | pg: { 34 | text: 'SELECT LEFT("post"."content", $1) FROM "post"', 35 | string: 'SELECT LEFT("post"."content", 4) FROM "post"' 36 | }, 37 | sqlite: { 38 | text: 'SELECT SUBSTR("post"."content", 1, $1) FROM "post"', 39 | string: 'SELECT SUBSTR("post"."content", 1, 4) FROM "post"' 40 | }, 41 | mysql: { 42 | text: 'SELECT LEFT(`post`.`content`, ?) FROM `post`', 43 | string: 'SELECT LEFT(`post`.`content`, 4) FROM `post`' 44 | }, 45 | mssql: { 46 | text: 'SELECT LEFT([post].[content], @1) FROM [post]', 47 | string: 'SELECT LEFT([post].[content], 4) FROM [post]' 48 | }, 49 | oracle: { 50 | text: 'SELECT LEFT("post"."content", :1) FROM "post"', 51 | string: 'SELECT LEFT("post"."content", 4) FROM "post"' 52 | }, 53 | params: [4] 54 | }); 55 | 56 | Harness.test({ 57 | query: post.select(instance.functions.RIGHT(post.content, 4)), 58 | pg: { 59 | text: 'SELECT RIGHT("post"."content", $1) FROM "post"', 60 | string: 'SELECT RIGHT("post"."content", 4) FROM "post"' 61 | }, 62 | sqlite: { 63 | text: 'SELECT SUBSTR("post"."content", -$1) FROM "post"', 64 | string: 'SELECT SUBSTR("post"."content", -4) FROM "post"' 65 | }, 66 | mysql: { 67 | text: 'SELECT RIGHT(`post`.`content`, ?) FROM `post`', 68 | string: 'SELECT RIGHT(`post`.`content`, 4) FROM `post`' 69 | }, 70 | mssql: { 71 | text: 'SELECT RIGHT([post].[content], @1) FROM [post]', 72 | string: 'SELECT RIGHT([post].[content], 4) FROM [post]' 73 | }, 74 | oracle: { 75 | text: 'SELECT RIGHT("post"."content", :1) FROM "post"', 76 | string: 'SELECT RIGHT("post"."content", 4) FROM "post"' 77 | }, 78 | params: [4] 79 | }); 80 | -------------------------------------------------------------------------------- /test/dialects/group-by-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const post = Harness.definePostTable(); 3 | 4 | Harness.test({ 5 | query: post.select(post.content).group(post.userId), 6 | pg: { 7 | text: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId"', 8 | string: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId"' 9 | }, 10 | sqlite: { 11 | text: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId"', 12 | string: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId"' 13 | }, 14 | mysql: { 15 | text: 'SELECT `post`.`content` FROM `post` GROUP BY `post`.`userId`', 16 | string: 'SELECT `post`.`content` FROM `post` GROUP BY `post`.`userId`' 17 | }, 18 | mssql: { 19 | text: 'SELECT [post].[content] FROM [post] GROUP BY [post].[userId]', 20 | string: 'SELECT [post].[content] FROM [post] GROUP BY [post].[userId]' 21 | }, 22 | oracle: { 23 | text: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId"', 24 | string: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId"' 25 | }, 26 | params: [] 27 | }); 28 | 29 | Harness.test({ 30 | query: post.select(post.content).group(post.userId, post.id), 31 | pg: { 32 | text: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId", "post"."id"', 33 | string: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId", "post"."id"' 34 | }, 35 | sqlite: { 36 | text: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId", "post"."id"', 37 | string: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId", "post"."id"' 38 | }, 39 | mysql: { 40 | text: 'SELECT `post`.`content` FROM `post` GROUP BY `post`.`userId`, `post`.`id`', 41 | string: 'SELECT `post`.`content` FROM `post` GROUP BY `post`.`userId`, `post`.`id`' 42 | }, 43 | mssql: { 44 | text: 'SELECT [post].[content] FROM [post] GROUP BY [post].[userId], [post].[id]', 45 | string: 'SELECT [post].[content] FROM [post] GROUP BY [post].[userId], [post].[id]' 46 | }, 47 | oracle: { 48 | text: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId", "post"."id"', 49 | string: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId", "post"."id"' 50 | }, 51 | params: [] 52 | }); 53 | 54 | Harness.test({ 55 | query: post.select(post.content.arrayAgg()).group(post.userId), 56 | pg: { 57 | text: 'SELECT array_agg("post"."content") AS "contents" FROM "post" GROUP BY "post"."userId"', 58 | string: 'SELECT array_agg("post"."content") AS "contents" FROM "post" GROUP BY "post"."userId"' 59 | }, 60 | sqlite: { 61 | text: 'SELECT GROUP_CONCAT("post"."content") AS "contents" FROM "post" GROUP BY "post"."userId"', 62 | string: 'SELECT GROUP_CONCAT("post"."content") AS "contents" FROM "post" GROUP BY "post"."userId"' 63 | }, 64 | mysql: { 65 | text: 'SELECT GROUP_CONCAT(`post`.`content`) AS `contents` FROM `post` GROUP BY `post`.`userId`', 66 | string: 'SELECT GROUP_CONCAT(`post`.`content`) AS `contents` FROM `post` GROUP BY `post`.`userId`' 67 | }, 68 | mssql: { 69 | text: 'SQL Server does not support array_agg.', 70 | throws: true 71 | }, 72 | oracle: { 73 | text: 'Oracle does not support array_agg.', 74 | throws: true 75 | }, 76 | params: [] 77 | }); 78 | 79 | Harness.test({ 80 | query: post.select(post.content.arrayAgg('post contents')).group(post.userId), 81 | pg: { 82 | text: 'SELECT array_agg("post"."content") AS "post contents" FROM "post" GROUP BY "post"."userId"', 83 | string: 'SELECT array_agg("post"."content") AS "post contents" FROM "post" GROUP BY "post"."userId"' 84 | }, 85 | sqlite: { 86 | text: 'SELECT GROUP_CONCAT("post"."content") AS "post contents" FROM "post" GROUP BY "post"."userId"', 87 | string: 'SELECT GROUP_CONCAT("post"."content") AS "post contents" FROM "post" GROUP BY "post"."userId"' 88 | }, 89 | mysql: { 90 | text: 'SELECT GROUP_CONCAT(`post`.`content`) AS `post contents` FROM `post` GROUP BY `post`.`userId`', 91 | string: 'SELECT GROUP_CONCAT(`post`.`content`) AS `post contents` FROM `post` GROUP BY `post`.`userId`' 92 | }, 93 | mssql: { 94 | text: 'SQL Server does not support array_agg.', 95 | throws: true 96 | }, 97 | oracle: { 98 | text: 'Oracle does not support array_agg.', 99 | throws: true 100 | }, 101 | params: [] 102 | }); 103 | 104 | Harness.test({ 105 | query: post.select(post.content).group([post.userId, post.id]), 106 | pg: { 107 | text: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId", "post"."id"', 108 | string: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId", "post"."id"' 109 | }, 110 | sqlite: { 111 | text: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId", "post"."id"', 112 | string: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId", "post"."id"' 113 | }, 114 | mysql: { 115 | text: 'SELECT `post`.`content` FROM `post` GROUP BY `post`.`userId`, `post`.`id`', 116 | string: 'SELECT `post`.`content` FROM `post` GROUP BY `post`.`userId`, `post`.`id`' 117 | }, 118 | mssql: { 119 | text: 'SELECT [post].[content] FROM [post] GROUP BY [post].[userId], [post].[id]', 120 | string: 'SELECT [post].[content] FROM [post] GROUP BY [post].[userId], [post].[id]' 121 | }, 122 | oracel: { 123 | text: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId", "post"."id"', 124 | string: 'SELECT "post"."content" FROM "post" GROUP BY "post"."userId", "post"."id"' 125 | }, 126 | params: [] 127 | }); 128 | -------------------------------------------------------------------------------- /test/dialects/having-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const post = Harness.definePostTable(); 3 | 4 | Harness.test({ 5 | query: post 6 | .select(post.userId, post.content.count()) 7 | .group(post.userId) 8 | .having(post.userId.gt(10)), 9 | pg: { 10 | text: 11 | 'SELECT "post"."userId", COUNT("post"."content") AS "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > $1)', 12 | string: 13 | 'SELECT "post"."userId", COUNT("post"."content") AS "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > 10)' 14 | }, 15 | sqlite: { 16 | text: 17 | 'SELECT "post"."userId", COUNT("post"."content") AS "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > $1)', 18 | string: 19 | 'SELECT "post"."userId", COUNT("post"."content") AS "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > 10)' 20 | }, 21 | mysql: { 22 | text: 23 | 'SELECT `post`.`userId`, COUNT(`post`.`content`) AS `content_count` FROM `post` GROUP BY `post`.`userId` HAVING (`post`.`userId` > ?)', 24 | string: 25 | 'SELECT `post`.`userId`, COUNT(`post`.`content`) AS `content_count` FROM `post` GROUP BY `post`.`userId` HAVING (`post`.`userId` > 10)' 26 | }, 27 | mssql: { 28 | text: 29 | 'SELECT [post].[userId], COUNT([post].[content]) AS [content_count] FROM [post] GROUP BY [post].[userId] HAVING ([post].[userId] > @1)', 30 | string: 31 | 'SELECT [post].[userId], COUNT([post].[content]) AS [content_count] FROM [post] GROUP BY [post].[userId] HAVING ([post].[userId] > 10)' 32 | }, 33 | oracle: { 34 | text: 35 | 'SELECT "post"."userId", COUNT("post"."content") "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > :1)', 36 | string: 37 | 'SELECT "post"."userId", COUNT("post"."content") "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > 10)' 38 | }, 39 | params: [10] 40 | }); 41 | 42 | Harness.test({ 43 | query: post 44 | .select(post.userId, post.content.count()) 45 | .group(post.userId) 46 | .having(post.userId.gt(10), post.userId.lt(100)), 47 | pg: { 48 | text: 49 | 'SELECT "post"."userId", COUNT("post"."content") AS "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > $1) AND ("post"."userId" < $2)', 50 | string: 51 | 'SELECT "post"."userId", COUNT("post"."content") AS "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > 10) AND ("post"."userId" < 100)' 52 | }, 53 | sqlite: { 54 | text: 55 | 'SELECT "post"."userId", COUNT("post"."content") AS "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > $1) AND ("post"."userId" < $2)', 56 | string: 57 | 'SELECT "post"."userId", COUNT("post"."content") AS "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > 10) AND ("post"."userId" < 100)' 58 | }, 59 | mysql: { 60 | text: 61 | 'SELECT `post`.`userId`, COUNT(`post`.`content`) AS `content_count` FROM `post` GROUP BY `post`.`userId` HAVING (`post`.`userId` > ?) AND (`post`.`userId` < ?)', 62 | string: 63 | 'SELECT `post`.`userId`, COUNT(`post`.`content`) AS `content_count` FROM `post` GROUP BY `post`.`userId` HAVING (`post`.`userId` > 10) AND (`post`.`userId` < 100)' 64 | }, 65 | mssql: { 66 | text: 67 | 'SELECT [post].[userId], COUNT([post].[content]) AS [content_count] FROM [post] GROUP BY [post].[userId] HAVING ([post].[userId] > @1) AND ([post].[userId] < @2)', 68 | string: 69 | 'SELECT [post].[userId], COUNT([post].[content]) AS [content_count] FROM [post] GROUP BY [post].[userId] HAVING ([post].[userId] > 10) AND ([post].[userId] < 100)' 70 | }, 71 | oracle: { 72 | text: 73 | 'SELECT "post"."userId", COUNT("post"."content") "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > :1) AND ("post"."userId" < :2)', 74 | string: 75 | 'SELECT "post"."userId", COUNT("post"."content") "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > 10) AND ("post"."userId" < 100)' 76 | }, 77 | params: [10, 100] 78 | }); 79 | 80 | Harness.test({ 81 | query: post 82 | .select(post.userId, post.content.count()) 83 | .group(post.userId) 84 | .having([post.userId.gt(10), post.userId.lt(100)]), 85 | pg: { 86 | text: 87 | 'SELECT "post"."userId", COUNT("post"."content") AS "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > $1) AND ("post"."userId" < $2)', 88 | string: 89 | 'SELECT "post"."userId", COUNT("post"."content") AS "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > 10) AND ("post"."userId" < 100)' 90 | }, 91 | sqlite: { 92 | text: 93 | 'SELECT "post"."userId", COUNT("post"."content") AS "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > $1) AND ("post"."userId" < $2)', 94 | string: 95 | 'SELECT "post"."userId", COUNT("post"."content") AS "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > 10) AND ("post"."userId" < 100)' 96 | }, 97 | mysql: { 98 | text: 99 | 'SELECT `post`.`userId`, COUNT(`post`.`content`) AS `content_count` FROM `post` GROUP BY `post`.`userId` HAVING (`post`.`userId` > ?) AND (`post`.`userId` < ?)', 100 | string: 101 | 'SELECT `post`.`userId`, COUNT(`post`.`content`) AS `content_count` FROM `post` GROUP BY `post`.`userId` HAVING (`post`.`userId` > 10) AND (`post`.`userId` < 100)' 102 | }, 103 | mssql: { 104 | text: 105 | 'SELECT [post].[userId], COUNT([post].[content]) AS [content_count] FROM [post] GROUP BY [post].[userId] HAVING ([post].[userId] > @1) AND ([post].[userId] < @2)', 106 | string: 107 | 'SELECT [post].[userId], COUNT([post].[content]) AS [content_count] FROM [post] GROUP BY [post].[userId] HAVING ([post].[userId] > 10) AND ([post].[userId] < 100)' 108 | }, 109 | oracle: { 110 | text: 111 | 'SELECT "post"."userId", COUNT("post"."content") "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > :1) AND ("post"."userId" < :2)', 112 | string: 113 | 'SELECT "post"."userId", COUNT("post"."content") "content_count" FROM "post" GROUP BY "post"."userId" HAVING ("post"."userId" > 10) AND ("post"."userId" < 100)' 114 | }, 115 | params: [10, 100] 116 | }); 117 | -------------------------------------------------------------------------------- /test/dialects/hstore-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | import { Sql } from '../../dist/lib.js'; 3 | const customer = Harness.defineCustomerTable(); 4 | const instance = new Sql('postgres'); 5 | 6 | Harness.test({ 7 | query: customer.update({ 8 | metadata: customer.metadata.concat(instance.functions.HSTORE('age', 20)) 9 | }), 10 | pg: { 11 | text: 'UPDATE "customer" SET "metadata" = ("customer"."metadata" || HSTORE($1, $2))', 12 | string: 'UPDATE "customer" SET "metadata" = ("customer"."metadata" || HSTORE(\'age\', 20))' 13 | }, 14 | params: ['age', 20] 15 | }); 16 | 17 | Harness.test({ 18 | query: customer.select(customer.metadata.key('age')), 19 | pg: { 20 | text: 'SELECT ("customer"."metadata" -> $1) FROM "customer"', 21 | string: 'SELECT ("customer"."metadata" -> \'age\') FROM "customer"' 22 | }, 23 | params: ['age'] 24 | }); 25 | -------------------------------------------------------------------------------- /test/dialects/ilike-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const post = Harness.definePostTable(); 3 | const user = Harness.defineUserTable(); 4 | 5 | Harness.test({ 6 | query: post.select(post.content, post.userId).where(post.content.ilike('A%')), 7 | pg: { 8 | text: 'SELECT "post"."content", "post"."userId" FROM "post" WHERE ("post"."content" ILIKE $1)', 9 | string: 'SELECT "post"."content", "post"."userId" FROM "post" WHERE ("post"."content" ILIKE \'A%\')' 10 | }, 11 | params: ['A%'] 12 | }); 13 | 14 | Harness.test({ 15 | query: post 16 | .insert(post.content, post.userId) 17 | .select("'test'", user.id) 18 | .from(user) 19 | .where(user.name.ilike('A%')), 20 | pg: { 21 | text: 'INSERT INTO "post" ("content", "userId") SELECT \'test\', "user"."id" FROM "user" WHERE ("user"."name" ILIKE $1)', 22 | string: 'INSERT INTO "post" ("content", "userId") SELECT \'test\', "user"."id" FROM "user" WHERE ("user"."name" ILIKE \'A%\')' 23 | }, 24 | params: ['A%'] 25 | }); 26 | 27 | Harness.test({ 28 | query: post 29 | .insert([post.content, post.userId]) 30 | .select("'test'", user.id) 31 | .from(user) 32 | .where(user.name.ilike('A%')), 33 | pg: { 34 | text: 'INSERT INTO "post" ("content", "userId") SELECT \'test\', "user"."id" FROM "user" WHERE ("user"."name" ILIKE $1)', 35 | string: 'INSERT INTO "post" ("content", "userId") SELECT \'test\', "user"."id" FROM "user" WHERE ("user"."name" ILIKE \'A%\')' 36 | }, 37 | params: ['A%'] 38 | }); 39 | 40 | Harness.test({ 41 | query: post 42 | .insert(post.userId) 43 | .select(user.id) 44 | .from(user) 45 | .where(user.name.ilike('A%')), 46 | pg: { 47 | text: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" WHERE ("user"."name" ILIKE $1)', 48 | string: 'INSERT INTO "post" ("userId") SELECT "user"."id" FROM "user" WHERE ("user"."name" ILIKE \'A%\')' 49 | }, 50 | params: ['A%'] 51 | }); 52 | -------------------------------------------------------------------------------- /test/dialects/join-to-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | import { Sql } from '../../dist/lib.js'; 3 | const instance = new Sql('postgres'); 4 | 5 | const user = instance.define({ 6 | name: 'user', 7 | columns: { 8 | id: { 9 | primaryKey: true 10 | } 11 | } 12 | }); 13 | 14 | const photo = instance.define({ 15 | name: 'photo', 16 | columns: { 17 | ownerId: { 18 | references: 'user' 19 | } 20 | } 21 | }); 22 | 23 | const post = instance.define({ 24 | name: 'post', 25 | columns: { 26 | id: { 27 | primaryKey: true 28 | }, 29 | ownerId: { 30 | references: { 31 | table: 'user', 32 | column: 'id' 33 | } 34 | } 35 | } 36 | }); 37 | 38 | Harness.test({ 39 | query: user.joinTo(post), 40 | pg: { 41 | text: '"user" INNER JOIN "post" ON ("user"."id" = "post"."ownerId")', 42 | string: '"user" INNER JOIN "post" ON ("user"."id" = "post"."ownerId")' 43 | }, 44 | sqlite: { 45 | text: '"user" INNER JOIN "post" ON ("user"."id" = "post"."ownerId")', 46 | string: '"user" INNER JOIN "post" ON ("user"."id" = "post"."ownerId")' 47 | }, 48 | mysql: { 49 | text: '`user` INNER JOIN `post` ON (`user`.`id` = `post`.`ownerId`)', 50 | string: '`user` INNER JOIN `post` ON (`user`.`id` = `post`.`ownerId`)' 51 | }, 52 | mssql: { 53 | text: '[user] INNER JOIN [post] ON ([user].[id] = [post].[ownerId])', 54 | string: '[user] INNER JOIN [post] ON ([user].[id] = [post].[ownerId])' 55 | }, 56 | oracle: { 57 | text: '"user" INNER JOIN "post" ON ("user"."id" = "post"."ownerId")', 58 | string: '"user" INNER JOIN "post" ON ("user"."id" = "post"."ownerId")' 59 | }, 60 | params: [] 61 | }); 62 | 63 | Harness.test({ 64 | query: post.joinTo(user), 65 | pg: { 66 | text: '"post" INNER JOIN "user" ON ("user"."id" = "post"."ownerId")', 67 | string: '"post" INNER JOIN "user" ON ("user"."id" = "post"."ownerId")' 68 | }, 69 | sqlite: { 70 | text: '"post" INNER JOIN "user" ON ("user"."id" = "post"."ownerId")', 71 | string: '"post" INNER JOIN "user" ON ("user"."id" = "post"."ownerId")' 72 | }, 73 | mysql: { 74 | text: '`post` INNER JOIN `user` ON (`user`.`id` = `post`.`ownerId`)', 75 | string: '`post` INNER JOIN `user` ON (`user`.`id` = `post`.`ownerId`)' 76 | }, 77 | mssql: { 78 | text: '[post] INNER JOIN [user] ON ([user].[id] = [post].[ownerId])', 79 | string: '[post] INNER JOIN [user] ON ([user].[id] = [post].[ownerId])' 80 | }, 81 | oracle: { 82 | text: '"post" INNER JOIN "user" ON ("user"."id" = "post"."ownerId")', 83 | string: '"post" INNER JOIN "user" ON ("user"."id" = "post"."ownerId")' 84 | }, 85 | params: [] 86 | }); 87 | 88 | Harness.test({ 89 | query: user.joinTo(photo), 90 | pg: { 91 | text: '"user" INNER JOIN "photo" ON ("user"."id" = "photo"."ownerId")', 92 | string: '"user" INNER JOIN "photo" ON ("user"."id" = "photo"."ownerId")' 93 | }, 94 | sqlite: { 95 | text: '"user" INNER JOIN "photo" ON ("user"."id" = "photo"."ownerId")', 96 | string: '"user" INNER JOIN "photo" ON ("user"."id" = "photo"."ownerId")' 97 | }, 98 | mysql: { 99 | text: '`user` INNER JOIN `photo` ON (`user`.`id` = `photo`.`ownerId`)', 100 | string: '`user` INNER JOIN `photo` ON (`user`.`id` = `photo`.`ownerId`)' 101 | }, 102 | mssql: { 103 | text: '[user] INNER JOIN [photo] ON ([user].[id] = [photo].[ownerId])', 104 | string: '[user] INNER JOIN [photo] ON ([user].[id] = [photo].[ownerId])' 105 | }, 106 | oracle: { 107 | text: '"user" INNER JOIN "photo" ON ("user"."id" = "photo"."ownerId")', 108 | string: '"user" INNER JOIN "photo" ON ("user"."id" = "photo"."ownerId")' 109 | }, 110 | params: [] 111 | }); 112 | -------------------------------------------------------------------------------- /test/dialects/json-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const customer = Harness.defineCustomerTable(); 3 | 4 | Harness.test({ 5 | query: customer.select(customer.metadata.key('age')), 6 | pg: { 7 | text: 'SELECT ("customer"."metadata" -> $1) FROM "customer"', 8 | string: 'SELECT ("customer"."metadata" -> \'age\') FROM "customer"' 9 | }, 10 | params: ['age'] 11 | }); 12 | 13 | Harness.test({ 14 | query: customer.select(customer.metadata.keyText('age')), 15 | pg: { 16 | text: 'SELECT ("customer"."metadata" ->> $1) FROM "customer"', 17 | string: 'SELECT ("customer"."metadata" ->> \'age\') FROM "customer"' 18 | }, 19 | params: ['age'] 20 | }); 21 | 22 | Harness.test({ 23 | query: customer.select(customer.metadata.path('{address,city}')), 24 | pg: { 25 | text: 'SELECT ("customer"."metadata" #> $1) FROM "customer"', 26 | string: 'SELECT ("customer"."metadata" #> \'{address,city}\') FROM "customer"' 27 | }, 28 | params: ['{address,city}'] 29 | }); 30 | 31 | Harness.test({ 32 | query: customer.select(customer.metadata.pathText('{address,city}')), 33 | pg: { 34 | text: 'SELECT ("customer"."metadata" #>> $1) FROM "customer"', 35 | string: 'SELECT ("customer"."metadata" #>> \'{address,city}\') FROM "customer"' 36 | }, 37 | params: ['{address,city}'] 38 | }); 39 | -------------------------------------------------------------------------------- /test/dialects/limit-and-offset-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const user = Harness.defineUserTable(); 3 | 4 | // For compatibility with PostgreSQL, MySQL also supports the LIMIT row_count OFFSET offset syntax. 5 | // http://dev.mysql.com/doc/refman/5.0/en/select.html 6 | 7 | Harness.test({ 8 | query: user 9 | .select(user.star()) 10 | .from(user) 11 | .order(user.name.asc()) 12 | .limit(1), 13 | pg: { 14 | text: 'SELECT "user".* FROM "user" ORDER BY "user"."name" LIMIT $1', 15 | string: 'SELECT "user".* FROM "user" ORDER BY "user"."name" LIMIT 1' 16 | }, 17 | sqlite: { 18 | text: 'SELECT "user".* FROM "user" ORDER BY "user"."name" LIMIT $1', 19 | string: 'SELECT "user".* FROM "user" ORDER BY "user"."name" LIMIT 1' 20 | }, 21 | mysql: { 22 | text: 'SELECT `user`.* FROM `user` ORDER BY `user`.`name` LIMIT ?', 23 | string: 'SELECT `user`.* FROM `user` ORDER BY `user`.`name` LIMIT 1' 24 | }, 25 | mssql: { 26 | text: 'SELECT TOP(@1) [user].* FROM [user] ORDER BY [user].[name]', 27 | string: 'SELECT TOP(1) [user].* FROM [user] ORDER BY [user].[name]' 28 | }, 29 | params: [1] 30 | }); 31 | 32 | Harness.test({ 33 | query: user 34 | .select(user.star()) 35 | .from(user) 36 | .order(user.name.asc()) 37 | .limit(3) 38 | .offset(6), 39 | pg: { 40 | text: 'SELECT "user".* FROM "user" ORDER BY "user"."name" LIMIT $1 OFFSET $2', 41 | string: 'SELECT "user".* FROM "user" ORDER BY "user"."name" LIMIT 3 OFFSET 6' 42 | }, 43 | sqlite: { 44 | text: 'SELECT "user".* FROM "user" ORDER BY "user"."name" LIMIT $1 OFFSET $2', 45 | string: 'SELECT "user".* FROM "user" ORDER BY "user"."name" LIMIT 3 OFFSET 6' 46 | }, 47 | mysql: { 48 | text: 'SELECT `user`.* FROM `user` ORDER BY `user`.`name` LIMIT ? OFFSET ?', 49 | string: 'SELECT `user`.* FROM `user` ORDER BY `user`.`name` LIMIT 3 OFFSET 6' 50 | }, 51 | params: [3, 6] 52 | }); 53 | 54 | Harness.test({ 55 | query: user 56 | .select(user.star()) 57 | .from(user) 58 | .order(user.name.asc()) 59 | .limit(3) 60 | .offset(6), 61 | mssql: { 62 | text: 'SELECT [user].* FROM [user] ORDER BY [user].[name] OFFSET @1 ROWS FETCH NEXT @2 ROWS ONLY', 63 | string: 'SELECT [user].* FROM [user] ORDER BY [user].[name] OFFSET 6 ROWS FETCH NEXT 3 ROWS ONLY' 64 | }, 65 | params: [6, 3] 66 | }); 67 | 68 | Harness.test({ 69 | query: user 70 | .select(user.star()) 71 | .from(user) 72 | .order(user.name.asc()) 73 | .offset(10), 74 | pg: { 75 | text: 'SELECT "user".* FROM "user" ORDER BY "user"."name" OFFSET $1', 76 | string: 'SELECT "user".* FROM "user" ORDER BY "user"."name" OFFSET 10' 77 | }, 78 | sqlite: { 79 | text: 'SELECT "user".* FROM "user" ORDER BY "user"."name" OFFSET $1', 80 | string: 'SELECT "user".* FROM "user" ORDER BY "user"."name" OFFSET 10' 81 | }, 82 | mysql: { 83 | text: 'SELECT `user`.* FROM `user` ORDER BY `user`.`name` OFFSET ?', 84 | string: 'SELECT `user`.* FROM `user` ORDER BY `user`.`name` OFFSET 10' 85 | }, 86 | mssql: { 87 | text: 'SELECT [user].* FROM [user] ORDER BY [user].[name] OFFSET @1 ROWS', 88 | string: 'SELECT [user].* FROM [user] ORDER BY [user].[name] OFFSET 10 ROWS' 89 | }, 90 | oracle: { 91 | text: 'SELECT "user".* FROM "user" ORDER BY "user"."name" OFFSET :1 ROWS', 92 | string: 'SELECT "user".* FROM "user" ORDER BY "user"."name" OFFSET 10 ROWS' 93 | }, 94 | params: [10] 95 | }); 96 | 97 | Harness.test({ 98 | query: user 99 | .select(user.star()) 100 | .where({ 101 | name: 'John' 102 | }) 103 | .offset( 104 | user 105 | .subQuery() 106 | .select('FLOOR(RANDOM() * COUNT(*))') 107 | .where({ 108 | name: 'John' 109 | }) 110 | ) 111 | .limit(1), 112 | pg: { 113 | text: 114 | 'SELECT "user".* FROM "user" WHERE ("user"."name" = $1) OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM "user" WHERE ("user"."name" = $2)) LIMIT $3', 115 | string: 116 | 'SELECT "user".* FROM "user" WHERE ("user"."name" = \'John\') OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM "user" WHERE ("user"."name" = \'John\')) LIMIT 1' 117 | }, 118 | sqlite: { 119 | text: 120 | 'SELECT "user".* FROM "user" WHERE ("user"."name" = $1) OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM "user" WHERE ("user"."name" = $2)) LIMIT $3', 121 | string: 122 | 'SELECT "user".* FROM "user" WHERE ("user"."name" = \'John\') OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM "user" WHERE ("user"."name" = \'John\')) LIMIT 1' 123 | }, 124 | mysql: { 125 | text: 126 | 'SELECT `user`.* FROM `user` WHERE (`user`.`name` = ?) OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM `user` WHERE (`user`.`name` = ?)) LIMIT ?', 127 | string: 128 | "SELECT `user`.* FROM `user` WHERE (`user`.`name` = 'John') OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM `user` WHERE (`user`.`name` = 'John')) LIMIT 1" 129 | }, 130 | mssql: { 131 | text: 'Microsoft SQL Server does not support OFFSET without and ORDER BY.', 132 | throws: true 133 | }, 134 | oracle: { 135 | text: 136 | 'SELECT "user".* FROM "user" WHERE ("user"."name" = :1) OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM "user" WHERE ("user"."name" = :2)) ROWS FETCH NEXT :3 ROWS ONLY', 137 | string: 138 | 'SELECT "user".* FROM "user" WHERE ("user"."name" = \'John\') OFFSET (SELECT FLOOR(RANDOM() * COUNT(*)) FROM "user" WHERE ("user"."name" = \'John\')) ROWS FETCH NEXT 1 ROWS ONLY' 139 | }, 140 | values: ['John', 'John', 1] 141 | }); 142 | 143 | // TODO: Should probably have a test case like the one above but including an ORDER BY clause so the mssql case can be tested 144 | -------------------------------------------------------------------------------- /test/dialects/literal-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | import { Sql } from '../../dist/lib.js'; 3 | const user = Harness.defineUserTable(); 4 | const instance = new Sql('postgres'); 5 | 6 | Harness.test({ 7 | query: user.select(instance.literal('foo'), user.name, instance.literal('123').as('onetwothree')), 8 | pg: { 9 | text: 'SELECT foo, "user"."name", 123 AS "onetwothree" FROM "user"', 10 | string: 'SELECT foo, "user"."name", 123 AS "onetwothree" FROM "user"' 11 | }, 12 | sqlite: { 13 | text: 'SELECT foo, "user"."name", 123 AS "onetwothree" FROM "user"', 14 | string: 'SELECT foo, "user"."name", 123 AS "onetwothree" FROM "user"' 15 | }, 16 | mysql: { 17 | text: 'SELECT foo, `user`.`name`, 123 AS `onetwothree` FROM `user`', 18 | string: 'SELECT foo, `user`.`name`, 123 AS `onetwothree` FROM `user`' 19 | }, 20 | oracle: { 21 | text: 'SELECT foo, "user"."name", 123 "onetwothree" FROM "user"', 22 | string: 'SELECT foo, "user"."name", 123 "onetwothree" FROM "user"' 23 | }, 24 | params: [] 25 | }); 26 | 27 | Harness.test({ 28 | query: user.select().where(instance.literal('foo = bar')), 29 | pg: { 30 | text: 'SELECT "user".* FROM "user" WHERE foo = bar', 31 | string: 'SELECT "user".* FROM "user" WHERE foo = bar' 32 | }, 33 | sqlite: { 34 | text: 'SELECT "user".* FROM "user" WHERE foo = bar', 35 | string: 'SELECT "user".* FROM "user" WHERE foo = bar' 36 | }, 37 | mysql: { 38 | text: 'SELECT `user`.* FROM `user` WHERE foo = bar', 39 | string: 'SELECT `user`.* FROM `user` WHERE foo = bar' 40 | }, 41 | oracle: { 42 | text: 'SELECT "user".* FROM "user" WHERE foo = bar', 43 | string: 'SELECT "user".* FROM "user" WHERE foo = bar' 44 | }, 45 | params: [] 46 | }); 47 | 48 | // A real world example: "How many records does page 3 have?" 49 | // This could be less than 10 (the limit) if we are on the last page. 50 | const subquery = user 51 | .subQuery<{ count_column: number }>('subquery_for_count') 52 | .select(instance.literal(1).as('count_column')) 53 | .limit(10) 54 | .offset(20); 55 | 56 | Harness.test({ 57 | query: user.select(subquery.count_column.count()).from(subquery), 58 | pg: { 59 | text: 60 | 'SELECT COUNT("subquery_for_count"."count_column") AS "count_column_count" FROM (SELECT 1 AS "count_column" FROM "user" LIMIT $1 OFFSET $2) "subquery_for_count"', 61 | string: 62 | 'SELECT COUNT("subquery_for_count"."count_column") AS "count_column_count" FROM (SELECT 1 AS "count_column" FROM "user" LIMIT 10 OFFSET 20) "subquery_for_count"' 63 | }, 64 | sqlite: { 65 | text: 66 | 'SELECT COUNT("subquery_for_count"."count_column") AS "count_column_count" FROM (SELECT 1 AS "count_column" FROM "user" LIMIT $1 OFFSET $2) "subquery_for_count"', 67 | string: 68 | 'SELECT COUNT("subquery_for_count"."count_column") AS "count_column_count" FROM (SELECT 1 AS "count_column" FROM "user" LIMIT 10 OFFSET 20) "subquery_for_count"' 69 | }, 70 | mysql: { 71 | text: 72 | 'SELECT COUNT(`subquery_for_count`.`count_column`) AS `count_column_count` FROM (SELECT 1 AS `count_column` FROM `user` LIMIT ? OFFSET ?) `subquery_for_count`', 73 | string: 74 | 'SELECT COUNT(`subquery_for_count`.`count_column`) AS `count_column_count` FROM (SELECT 1 AS `count_column` FROM `user` LIMIT 10 OFFSET 20) `subquery_for_count`' 75 | }, 76 | oracle: { 77 | text: 78 | 'SELECT COUNT("subquery_for_count"."count_column") "count_column_count" FROM (SELECT 1 "count_column" FROM "user" OFFSET :2 ROWS FETCH NEXT :1 ROWS ONLY) "subquery_for_count"', 79 | string: 80 | 'SELECT COUNT("subquery_for_count"."count_column") "count_column_count" FROM (SELECT 1 "count_column" FROM "user" OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY) "subquery_for_count"' 81 | }, 82 | params: [10, 20] 83 | }); 84 | -------------------------------------------------------------------------------- /test/dialects/matches-test.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | import { Sql } from '../../dist/lib.js'; 3 | const post = Harness.definePostTable(); 4 | const instance = new Sql('postgres'); 5 | 6 | // Postgres needs the to_tsquery function to use with @@ operator 7 | Harness.test({ 8 | query: post.select(post.star()).where(post.content.match(instance.functions.TO_TSQUERY('hello'))), 9 | pg: { 10 | text: 'SELECT "post".* FROM "post" WHERE ("post"."content" @@ TO_TSQUERY($1))', 11 | string: 'SELECT "post".* FROM "post" WHERE ("post"."content" @@ TO_TSQUERY(\'hello\'))' 12 | }, 13 | params: ['hello'] 14 | }); 15 | 16 | Harness.test({ 17 | query: post.select(post.star()).where(post.content.match('hello')), 18 | sqlite: { 19 | text: 'SELECT "post".* FROM "post" WHERE ("post"."content" MATCH $1)', 20 | string: 'SELECT "post".* FROM "post" WHERE ("post"."content" MATCH \'hello\')' 21 | }, 22 | mysql: { 23 | text: 'SELECT `post`.* FROM `post` WHERE (MATCH `post`.`content` AGAINST ?)', 24 | string: "SELECT `post`.* FROM `post` WHERE (MATCH `post`.`content` AGAINST 'hello')" 25 | }, 26 | mssql: { 27 | text: 'SELECT [post].* FROM [post] WHERE (CONTAINS ([post].[content], @1))', 28 | string: "SELECT [post].* FROM [post] WHERE (CONTAINS ([post].[content], 'hello'))" 29 | }, 30 | oracle: { 31 | text: 'SELECT "post".* FROM "post" WHERE (INSTR ("post"."content", :1) > 0)', 32 | string: 'SELECT "post".* FROM "post" WHERE (INSTR ("post"."content", \'hello\') > 0)' 33 | }, 34 | params: ['hello'] 35 | }); 36 | 37 | // matches, ordered by best rank first 38 | Harness.test({ 39 | query: post 40 | .select(post.id, instance.functions.TS_RANK_CD(post.content, instance.functions.TO_TSQUERY('hello')).as('rank')) 41 | .where(post.content.match(instance.functions.TO_TSQUERY('hello'))) 42 | .order(instance.functions.TS_RANK_CD(post.content, instance.functions.TO_TSQUERY('hello')).descending()), 43 | pg: { 44 | text: 45 | 'SELECT "post"."id", TS_RANK_CD("post"."content", TO_TSQUERY($1)) AS "rank" FROM "post" WHERE ("post"."content" @@ TO_TSQUERY($2)) ORDER BY TS_RANK_CD("post"."content", TO_TSQUERY($3)) DESC', 46 | string: 47 | 'SELECT "post"."id", TS_RANK_CD("post"."content", TO_TSQUERY(\'hello\')) AS "rank" FROM "post" WHERE ("post"."content" @@ TO_TSQUERY(\'hello\')) ORDER BY TS_RANK_CD("post"."content", TO_TSQUERY(\'hello\')) DESC' 48 | }, 49 | params: ['hello', 'hello', 'hello'] 50 | }); 51 | -------------------------------------------------------------------------------- /test/dialects/namespace-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | import { Table } from '../../dist/lib.js'; 3 | const user = Harness.defineUserTable(); 4 | const post = Harness.definePostTable(); 5 | 6 | const u = user.as('u'); 7 | Harness.test({ 8 | query: u.select(u.name).from(u), 9 | pg: { 10 | text: 'SELECT "u"."name" FROM "user" AS "u"', 11 | string: 'SELECT "u"."name" FROM "user" AS "u"' 12 | }, 13 | sqlite: { 14 | text: 'SELECT "u"."name" FROM "user" AS "u"', 15 | string: 'SELECT "u"."name" FROM "user" AS "u"' 16 | }, 17 | mysql: { 18 | text: 'SELECT `u`.`name` FROM `user` AS `u`', 19 | string: 'SELECT `u`.`name` FROM `user` AS `u`' 20 | }, 21 | mssql: { 22 | text: 'SELECT [u].[name] FROM [user] AS [u]', 23 | string: 'SELECT [u].[name] FROM [user] AS [u]' 24 | }, 25 | oracle: { 26 | text: 'SELECT "u"."name" FROM "user" "u"', 27 | string: 'SELECT "u"."name" FROM "user" "u"' 28 | }, 29 | params: [] 30 | }); 31 | 32 | Harness.test({ 33 | query: u.select(u.star()).from(u), 34 | pg: { 35 | text: 'SELECT "u".* FROM "user" AS "u"', 36 | string: 'SELECT "u".* FROM "user" AS "u"' 37 | }, 38 | sqlite: { 39 | text: 'SELECT "u".* FROM "user" AS "u"', 40 | string: 'SELECT "u".* FROM "user" AS "u"' 41 | }, 42 | mysql: { 43 | text: 'SELECT `u`.* FROM `user` AS `u`', 44 | string: 'SELECT `u`.* FROM `user` AS `u`' 45 | }, 46 | mssql: { 47 | text: 'SELECT [u].* FROM [user] AS [u]', 48 | string: 'SELECT [u].* FROM [user] AS [u]' 49 | }, 50 | oracle: { 51 | text: 'SELECT "u".* FROM "user" "u"', 52 | string: 'SELECT "u".* FROM "user" "u"' 53 | }, 54 | params: [] 55 | }); 56 | 57 | const p = post.as('p'); 58 | Harness.test({ 59 | query: u.select(u.name).from(u.join(p).on(u.id.equals(p.userId).and(p.id.equals(3)))), 60 | pg: { 61 | text: 'SELECT "u"."name" FROM "user" AS "u" INNER JOIN "post" AS "p" ON (("u"."id" = "p"."userId") AND ("p"."id" = $1))', 62 | string: 'SELECT "u"."name" FROM "user" AS "u" INNER JOIN "post" AS "p" ON (("u"."id" = "p"."userId") AND ("p"."id" = 3))' 63 | }, 64 | sqlite: { 65 | text: 'SELECT "u"."name" FROM "user" AS "u" INNER JOIN "post" AS "p" ON (("u"."id" = "p"."userId") AND ("p"."id" = $1))', 66 | string: 'SELECT "u"."name" FROM "user" AS "u" INNER JOIN "post" AS "p" ON (("u"."id" = "p"."userId") AND ("p"."id" = 3))' 67 | }, 68 | mysql: { 69 | text: 'SELECT `u`.`name` FROM `user` AS `u` INNER JOIN `post` AS `p` ON ((`u`.`id` = `p`.`userId`) AND (`p`.`id` = ?))', 70 | string: 'SELECT `u`.`name` FROM `user` AS `u` INNER JOIN `post` AS `p` ON ((`u`.`id` = `p`.`userId`) AND (`p`.`id` = 3))' 71 | }, 72 | mssql: { 73 | text: 'SELECT [u].[name] FROM [user] AS [u] INNER JOIN [post] AS [p] ON (([u].[id] = [p].[userId]) AND ([p].[id] = @1))', 74 | string: 'SELECT [u].[name] FROM [user] AS [u] INNER JOIN [post] AS [p] ON (([u].[id] = [p].[userId]) AND ([p].[id] = 3))' 75 | }, 76 | oracle: { 77 | text: 'SELECT "u"."name" FROM "user" "u" INNER JOIN "post" "p" ON (("u"."id" = "p"."userId") AND ("p"."id" = :1))', 78 | string: 'SELECT "u"."name" FROM "user" "u" INNER JOIN "post" "p" ON (("u"."id" = "p"."userId") AND ("p"."id" = 3))' 79 | }, 80 | params: [3] 81 | }); 82 | 83 | Harness.test({ 84 | query: u.select(p.content, u.name).from(u.join(p).on(u.id.equals(p.userId).and(p.content.isNotNull()))), 85 | pg: { 86 | text: 87 | 'SELECT "p"."content", "u"."name" FROM "user" AS "u" INNER JOIN "post" AS "p" ON (("u"."id" = "p"."userId") AND ("p"."content" IS NOT NULL))', 88 | string: 89 | 'SELECT "p"."content", "u"."name" FROM "user" AS "u" INNER JOIN "post" AS "p" ON (("u"."id" = "p"."userId") AND ("p"."content" IS NOT NULL))' 90 | }, 91 | sqlite: { 92 | text: 93 | 'SELECT "p"."content", "u"."name" FROM "user" AS "u" INNER JOIN "post" AS "p" ON (("u"."id" = "p"."userId") AND ("p"."content" IS NOT NULL))', 94 | string: 95 | 'SELECT "p"."content", "u"."name" FROM "user" AS "u" INNER JOIN "post" AS "p" ON (("u"."id" = "p"."userId") AND ("p"."content" IS NOT NULL))' 96 | }, 97 | mysql: { 98 | text: 99 | 'SELECT `p`.`content`, `u`.`name` FROM `user` AS `u` INNER JOIN `post` AS `p` ON ((`u`.`id` = `p`.`userId`) AND (`p`.`content` IS NOT NULL))', 100 | string: 101 | 'SELECT `p`.`content`, `u`.`name` FROM `user` AS `u` INNER JOIN `post` AS `p` ON ((`u`.`id` = `p`.`userId`) AND (`p`.`content` IS NOT NULL))' 102 | }, 103 | mssql: { 104 | text: 105 | 'SELECT [p].[content], [u].[name] FROM [user] AS [u] INNER JOIN [post] AS [p] ON (([u].[id] = [p].[userId]) AND ([p].[content] IS NOT NULL))', 106 | string: 107 | 'SELECT [p].[content], [u].[name] FROM [user] AS [u] INNER JOIN [post] AS [p] ON (([u].[id] = [p].[userId]) AND ([p].[content] IS NOT NULL))' 108 | }, 109 | oracle: { 110 | text: 111 | 'SELECT "p"."content", "u"."name" FROM "user" "u" INNER JOIN "post" "p" ON (("u"."id" = "p"."userId") AND ("p"."content" IS NOT NULL))', 112 | string: 113 | 'SELECT "p"."content", "u"."name" FROM "user" "u" INNER JOIN "post" "p" ON (("u"."id" = "p"."userId") AND ("p"."content" IS NOT NULL))' 114 | }, 115 | params: [] 116 | }); 117 | 118 | // the quote property isn't implemented for columns, so all columns are quoted in generated queries 119 | const comment = Table.define<{ text: string; userId: number }>({ 120 | name: 'comment', 121 | columns: [ 122 | { 123 | name: 'text' 124 | }, 125 | { 126 | name: 'userId' 127 | } 128 | ] 129 | }); 130 | 131 | Harness.test({ 132 | query: comment.select(comment.text, comment.userId), 133 | pg: { 134 | text: 'SELECT "comment"."text", "comment"."userId" FROM "comment"', 135 | string: 'SELECT "comment"."text", "comment"."userId" FROM "comment"' 136 | }, 137 | sqlite: { 138 | text: 'SELECT "comment"."text", "comment"."userId" FROM "comment"', 139 | string: 'SELECT "comment"."text", "comment"."userId" FROM "comment"' 140 | }, 141 | mysql: { 142 | text: 'SELECT `comment`.`text`, `comment`.`userId` FROM `comment`', 143 | string: 'SELECT `comment`.`text`, `comment`.`userId` FROM `comment`' 144 | }, 145 | mssql: { 146 | text: 'SELECT [comment].[text], [comment].[userId] FROM [comment]', 147 | string: 'SELECT [comment].[text], [comment].[userId] FROM [comment]' 148 | }, 149 | orcle: { 150 | text: 'SELECT "comment"."text", "comment"."userId" FROM "comment"', 151 | string: 'SELECT "comment"."text", "comment"."userId" FROM "comment"' 152 | }, 153 | params: [] 154 | }); 155 | -------------------------------------------------------------------------------- /test/dialects/regex-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const customer = Harness.defineCustomerTable(); 3 | 4 | Harness.test({ 5 | query: customer.select(customer.metadata.regex('age')), 6 | pg: { 7 | text: 'SELECT ("customer"."metadata" ~ $1) FROM "customer"', 8 | string: 'SELECT ("customer"."metadata" ~ \'age\') FROM "customer"' 9 | }, 10 | params: ['age'] 11 | }); 12 | 13 | Harness.test({ 14 | query: customer.select(customer.metadata.iregex('age')), 15 | pg: { 16 | text: 'SELECT ("customer"."metadata" ~* $1) FROM "customer"', 17 | string: 'SELECT ("customer"."metadata" ~* \'age\') FROM "customer"' 18 | }, 19 | params: ['age'] 20 | }); 21 | 22 | Harness.test({ 23 | query: customer.select(customer.metadata.notRegex('age')), 24 | pg: { 25 | text: 'SELECT ("customer"."metadata" !~ $1) FROM "customer"', 26 | string: 'SELECT ("customer"."metadata" !~ \'age\') FROM "customer"' 27 | }, 28 | params: ['age'] 29 | }); 30 | 31 | Harness.test({ 32 | query: customer.select(customer.metadata.notIregex('age')), 33 | pg: { 34 | text: 'SELECT ("customer"."metadata" !~* $1) FROM "customer"', 35 | string: 'SELECT ("customer"."metadata" !~* \'age\') FROM "customer"' 36 | }, 37 | params: ['age'] 38 | }); 39 | -------------------------------------------------------------------------------- /test/dialects/returning-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const user = Harness.defineUserTable(); 3 | 4 | Harness.test({ 5 | query: user.insert({ name: 'joe' }).returning(), 6 | pg: { 7 | text: 'INSERT INTO "user" ("name") VALUES ($1) RETURNING *', 8 | string: 'INSERT INTO "user" ("name") VALUES (\'joe\') RETURNING *' 9 | }, 10 | params: ['joe'] 11 | }); 12 | 13 | Harness.test({ 14 | query: user.insert({ name: 'joe' }).returning('id'), 15 | pg: { 16 | text: 'INSERT INTO "user" ("name") VALUES ($1) RETURNING id', 17 | string: 'INSERT INTO "user" ("name") VALUES (\'joe\') RETURNING id' 18 | }, 19 | params: ['joe'] 20 | }); 21 | -------------------------------------------------------------------------------- /test/dialects/row-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | import { Sql } from '../../dist/lib.js'; 3 | const post = Harness.definePostTable(); 4 | const instance = new Sql('postgres'); 5 | 6 | // Array columns 7 | Harness.test({ 8 | query: instance.row(post.userId, 'some new content', instance.array('nodejs')), 9 | pg: { 10 | text: '("post"."userId", $1, ARRAY[$2])', 11 | string: '("post"."userId", \'some new content\', ARRAY[\'nodejs\'])' 12 | }, 13 | params: ['some new content', 'nodejs'] 14 | }); 15 | 16 | Harness.test({ 17 | query: post.select(post.star()).where(instance.row(post.userId, post.content).equals(instance.row(1234, 'some new content'))), 18 | pg: { 19 | text: 'SELECT "post".* FROM "post" WHERE (("post"."userId", "post"."content") = ($1, $2))', 20 | string: 'SELECT "post".* FROM "post" WHERE (("post"."userId", "post"."content") = (1234, \'some new content\'))' 21 | }, 22 | params: [1234, 'some new content'] 23 | }); 24 | 25 | Harness.test({ 26 | query: post.select(post.star()).where(instance.row(post.userId, post.content).in([instance.row(1234, 'some new content'), instance.row(5678, 'some other content')])), 27 | pg: { 28 | text: 'SELECT "post".* FROM "post" WHERE (("post"."userId", "post"."content") IN (($1, $2), ($3, $4)))', 29 | string: 'SELECT "post".* FROM "post" WHERE (("post"."userId", "post"."content") IN ((1234, \'some new content\'), (5678, \'some other content\')))' 30 | }, 31 | params: [1234, 'some new content', 5678, 'some other content'] 32 | }); 33 | -------------------------------------------------------------------------------- /test/dialects/shortcut-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const post = Harness.definePostTable(); 3 | const user = Harness.defineUserTable(); 4 | 5 | // shortcut: 'select * from ' 6 | Harness.test({ 7 | query: user, 8 | pg: { 9 | text: 'SELECT "user".* FROM "user"', 10 | string: 'SELECT "user".* FROM "user"' 11 | }, 12 | sqlite: { 13 | text: 'SELECT "user".* FROM "user"', 14 | string: 'SELECT "user".* FROM "user"' 15 | }, 16 | mysql: { 17 | text: 'SELECT `user`.* FROM `user`', 18 | string: 'SELECT `user`.* FROM `user`' 19 | }, 20 | mssql: { 21 | text: 'SELECT [user].* FROM [user]', 22 | string: 'SELECT [user].* FROM [user]' 23 | }, 24 | oracle: { 25 | text: 'SELECT "user".* FROM "user"', 26 | string: 'SELECT "user".* FROM "user"' 27 | }, 28 | params: [] 29 | }); 30 | 31 | Harness.test({ 32 | query: user.where(user.name.equals(3)), 33 | pg: { 34 | text: 'SELECT * FROM "user" WHERE ("user"."name" = $1)', 35 | string: 'SELECT * FROM "user" WHERE ("user"."name" = 3)' 36 | }, 37 | sqlite: { 38 | text: 'SELECT * FROM "user" WHERE ("user"."name" = $1)', 39 | string: 'SELECT * FROM "user" WHERE ("user"."name" = 3)' 40 | }, 41 | mysql: { 42 | text: 'SELECT * FROM `user` WHERE (`user`.`name` = ?)', 43 | string: 'SELECT * FROM `user` WHERE (`user`.`name` = 3)' 44 | }, 45 | mssql: { 46 | text: 'SELECT * FROM [user] WHERE ([user].[name] = @1)', 47 | string: 'SELECT * FROM [user] WHERE ([user].[name] = 3)' 48 | }, 49 | oracle: { 50 | text: 'SELECT * FROM "user" WHERE ("user"."name" = :1)', 51 | string: 'SELECT * FROM "user" WHERE ("user"."name" = 3)' 52 | }, 53 | params: [3] 54 | }); 55 | 56 | Harness.test({ 57 | query: user.where(user.name.equals(3)).where(user.id.equals(1)), 58 | pg: { 59 | text: 'SELECT * FROM "user" WHERE (("user"."name" = $1) AND ("user"."id" = $2))', 60 | string: 'SELECT * FROM "user" WHERE (("user"."name" = 3) AND ("user"."id" = 1))' 61 | }, 62 | sqlite: { 63 | text: 'SELECT * FROM "user" WHERE (("user"."name" = $1) AND ("user"."id" = $2))', 64 | string: 'SELECT * FROM "user" WHERE (("user"."name" = 3) AND ("user"."id" = 1))' 65 | }, 66 | mysql: { 67 | text: 'SELECT * FROM `user` WHERE ((`user`.`name` = ?) AND (`user`.`id` = ?))', 68 | string: 'SELECT * FROM `user` WHERE ((`user`.`name` = 3) AND (`user`.`id` = 1))' 69 | }, 70 | mssql: { 71 | text: 'SELECT * FROM [user] WHERE (([user].[name] = @1) AND ([user].[id] = @2))', 72 | string: 'SELECT * FROM [user] WHERE (([user].[name] = 3) AND ([user].[id] = 1))' 73 | }, 74 | oracle: { 75 | text: 'SELECT * FROM "user" WHERE (("user"."name" = :1) AND ("user"."id" = :2))', 76 | string: 'SELECT * FROM "user" WHERE (("user"."name" = 3) AND ("user"."id" = 1))' 77 | }, 78 | params: [3, 1] 79 | }); 80 | 81 | // shortcut: no 'from' 82 | Harness.test({ 83 | query: post.select(post.content), 84 | pg: { 85 | text: 'SELECT "post"."content" FROM "post"', 86 | string: 'SELECT "post"."content" FROM "post"' 87 | }, 88 | sqlite: { 89 | text: 'SELECT "post"."content" FROM "post"', 90 | string: 'SELECT "post"."content" FROM "post"' 91 | }, 92 | mysql: { 93 | text: 'SELECT `post`.`content` FROM `post`', 94 | string: 'SELECT `post`.`content` FROM `post`' 95 | }, 96 | mssql: { 97 | text: 'SELECT [post].[content] FROM [post]', 98 | string: 'SELECT [post].[content] FROM [post]' 99 | }, 100 | oracle: { 101 | text: 'SELECT "post"."content" FROM "post"', 102 | string: 'SELECT "post"."content" FROM "post"' 103 | }, 104 | params: [] 105 | }); 106 | 107 | Harness.test({ 108 | query: post.select(post.content).where(post.userId.equals(1)), 109 | pg: { 110 | text: 'SELECT "post"."content" FROM "post" WHERE ("post"."userId" = $1)', 111 | string: 'SELECT "post"."content" FROM "post" WHERE ("post"."userId" = 1)' 112 | }, 113 | sqlite: { 114 | text: 'SELECT "post"."content" FROM "post" WHERE ("post"."userId" = $1)', 115 | string: 'SELECT "post"."content" FROM "post" WHERE ("post"."userId" = 1)' 116 | }, 117 | mysql: { 118 | text: 'SELECT `post`.`content` FROM `post` WHERE (`post`.`userId` = ?)', 119 | string: 'SELECT `post`.`content` FROM `post` WHERE (`post`.`userId` = 1)' 120 | }, 121 | mssql: { 122 | text: 'SELECT [post].[content] FROM [post] WHERE ([post].[userId] = @1)', 123 | string: 'SELECT [post].[content] FROM [post] WHERE ([post].[userId] = 1)' 124 | }, 125 | oracle: { 126 | text: 'SELECT "post"."content" FROM "post" WHERE ("post"."userId" = :1)', 127 | string: 'SELECT "post"."content" FROM "post" WHERE ("post"."userId" = 1)' 128 | }, 129 | params: [1] 130 | }); 131 | 132 | Harness.test({ 133 | query: post 134 | .where(post.content.isNull()) 135 | .or({ 136 | content: '' 137 | }) 138 | .and({ 139 | userId: 1 140 | }), 141 | pg: { 142 | text: 'SELECT * FROM "post" WHERE ((("post"."content" IS NULL) OR ("post"."content" = $1)) AND ("post"."userId" = $2))', 143 | string: 'SELECT * FROM "post" WHERE ((("post"."content" IS NULL) OR ("post"."content" = \'\')) AND ("post"."userId" = 1))' 144 | }, 145 | sqlite: { 146 | text: 'SELECT * FROM "post" WHERE ((("post"."content" IS NULL) OR ("post"."content" = $1)) AND ("post"."userId" = $2))', 147 | string: 'SELECT * FROM "post" WHERE ((("post"."content" IS NULL) OR ("post"."content" = \'\')) AND ("post"."userId" = 1))' 148 | }, 149 | mysql: { 150 | text: 'SELECT * FROM `post` WHERE (((`post`.`content` IS NULL) OR (`post`.`content` = ?)) AND (`post`.`userId` = ?))', 151 | string: "SELECT * FROM `post` WHERE (((`post`.`content` IS NULL) OR (`post`.`content` = '')) AND (`post`.`userId` = 1))" 152 | }, 153 | mssql: { 154 | text: 'SELECT * FROM [post] WHERE ((([post].[content] IS NULL) OR ([post].[content] = @1)) AND ([post].[userId] = @2))', 155 | string: "SELECT * FROM [post] WHERE ((([post].[content] IS NULL) OR ([post].[content] = '')) AND ([post].[userId] = 1))" 156 | }, 157 | oracle: { 158 | text: 'SELECT * FROM "post" WHERE ((("post"."content" IS NULL) OR ("post"."content" = :1)) AND ("post"."userId" = :2))', 159 | string: 'SELECT * FROM "post" WHERE ((("post"."content" IS NULL) OR ("post"."content" = \'\')) AND ("post"."userId" = 1))' 160 | }, 161 | params: ['', 1] 162 | }); 163 | -------------------------------------------------------------------------------- /test/dialects/subfield-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const customerComposite = Harness.defineCustomerCompositeTable(); 3 | 4 | Harness.test({ 5 | query: customerComposite.select(customerComposite.info.subfields.age), 6 | pg: { 7 | text: 'SELECT ("customer"."info")."age" FROM "customer"', 8 | string: 'SELECT ("customer"."info")."age" FROM "customer"' 9 | }, 10 | params: [] 11 | }); 12 | 13 | Harness.test({ 14 | query: customerComposite.select(customerComposite.info.subfields.age.as('years')), 15 | pg: { 16 | text: 'SELECT ("customer"."info")."age" AS "years" FROM "customer"', 17 | string: 'SELECT ("customer"."info")."age" AS "years" FROM "customer"' 18 | }, 19 | params: [] 20 | }); 21 | 22 | Harness.test({ 23 | query: customerComposite.select(customerComposite.id).where(customerComposite.info.subfields.salary.equals(10)), 24 | pg: { 25 | text: 'SELECT "customer"."id" FROM "customer" WHERE (("customer"."info")."salary" = $1)', 26 | string: 'SELECT "customer"."id" FROM "customer" WHERE (("customer"."info")."salary" = 10)' 27 | }, 28 | params: [10] 29 | }); 30 | 31 | Harness.test({ 32 | query: customerComposite.select(customerComposite.info.subfields.name.distinct()), 33 | pg: { 34 | text: 'SELECT DISTINCT(("customer"."info")."name") FROM "customer"', 35 | string: 'SELECT DISTINCT(("customer"."info")."name") FROM "customer"' 36 | }, 37 | params: [] 38 | }); 39 | -------------------------------------------------------------------------------- /test/dialects/support.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | import { Mssql, Mysql, Oracle, Postgres, Sqlite, Table } from '../../dist/lib.js'; 4 | 5 | // specify dialect classes 6 | const dialects = { 7 | pg: Postgres, 8 | sqlite: Sqlite, 9 | mysql: Mysql, 10 | mssql: Mssql, 11 | oracle: Oracle 12 | }; 13 | 14 | function customTest(expected: any) { 15 | // for each dialect 16 | Object.keys(dialects).forEach(function(dialect) { 17 | const expectedObject = expected[dialect]; 18 | if (undefined !== expectedObject) { 19 | const DialectClass = (dialects as any)[dialect]; 20 | 21 | const title = dialect + ': ' + (expected.title || expectedObject.text || expectedObject); 22 | test(title, function() { 23 | // check if this query is expected to throw 24 | if (expectedObject.throws) { 25 | assert.throws(function() { 26 | new DialectClass(expectedObject.config || {}).getQuery(expected.query); 27 | }); 28 | } else { 29 | // build query for dialect 30 | const compiledQuery = new DialectClass(expectedObject.config || {}).getQuery(expected.query); 31 | 32 | // test result is correct 33 | const expectedText = expectedObject.text || expectedObject; 34 | assert.strictEqual(compiledQuery.text, expectedText); 35 | 36 | // if params are specified then test these are correct 37 | const expectedParams = expectedObject.params || expected.params; 38 | if (undefined !== expectedParams) { 39 | assert.strictEqual(expectedParams.length, compiledQuery.values.length, 'params length'); 40 | for (let i = 0; i < expectedParams.length; i++) { 41 | assert.deepStrictEqual(expectedParams[i], compiledQuery.values[i], 'param ' + (i + 1)); 42 | } 43 | } 44 | } 45 | 46 | if (undefined !== expectedObject.string) { 47 | // test the toString 48 | if (expectedObject.throws) { 49 | assert.throws(function() { 50 | new DialectClass(expectedObject.config || {}).getString(expected.query); 51 | }); 52 | } else { 53 | const compiledString = new DialectClass(expectedObject.config || {}).getString(expected.query); 54 | 55 | // test result is correct 56 | assert.strictEqual(compiledString, expectedObject.string); 57 | } 58 | } 59 | }); 60 | } // if 61 | }); // forEach 62 | } 63 | 64 | export interface UserTable { 65 | id: number; 66 | name: string; 67 | } 68 | 69 | export function defineUserTable() { 70 | return Table.define({ 71 | name: 'user', 72 | columns: ['id', 'name'] 73 | }); 74 | } 75 | 76 | export interface PostTable { 77 | id: number; 78 | userId: number; 79 | content: string | null; 80 | tags: string; 81 | length: number; 82 | } 83 | 84 | export function definePostTable() { 85 | return Table.define({ 86 | name: 'post', 87 | columns: ['id', 'userId', 'content', 'tags', 'length'] 88 | }); 89 | } 90 | 91 | export interface CommentTable { 92 | postId: number; 93 | text: string; 94 | } 95 | 96 | export function defineCommentTable() { 97 | return Table.define({ 98 | name: 'comment', 99 | columns: ['postId', 'text'] 100 | }); 101 | } 102 | 103 | export interface CustomerTable { 104 | id: number; 105 | name: string; 106 | age: number; 107 | income: number; 108 | metadata: Record; 109 | } 110 | 111 | export function defineCustomerTable() { 112 | return Table.define({ 113 | name: 'customer', 114 | columns: ['id', 'name', 'age', 'income', 'metadata'] 115 | }); 116 | } 117 | 118 | export interface CustomerCompositeTable { 119 | id: number; 120 | info: Record; 121 | } 122 | 123 | // This table defines the customer attributes as a composite field 124 | export function defineCustomerCompositeTable() { 125 | return Table.define({ 126 | name: 'customer', 127 | columns: { 128 | id: {}, 129 | info: { subfields: ['name', 'age', 'salary'] } 130 | } 131 | }); 132 | } 133 | 134 | export interface CustomerAliasTable { 135 | id_alias: number; 136 | name_alias: string; 137 | age_alias: number; 138 | income_alias: number; 139 | metadata_alias: Record; 140 | } 141 | 142 | export function defineCustomerAliasTable() { 143 | return Table.define({ 144 | name: 'customer', 145 | columns: { 146 | id: { property: 'id_alias' }, 147 | name: { property: 'name_alias' }, 148 | age: { property: 'age_alias' }, 149 | income: { property: 'income_alias' }, 150 | metadata: { property: 'metadata_alias' } 151 | } 152 | }); 153 | } 154 | 155 | export interface VariableTable { 156 | a: any; 157 | b: any; 158 | c: any; 159 | d: any; 160 | t: any; 161 | u: any; 162 | v: any; 163 | x: any; 164 | y: any; 165 | z: any; 166 | } 167 | 168 | // This table contains column names that correspond to popularly used variables in formulas. 169 | export function defineVariableTable() { 170 | return Table.define({ 171 | name: 'variable', 172 | columns: ['a', 'b', 'c', 'd', 't', 'u', 'v', 'x', 'y', 'z'] 173 | }); 174 | } 175 | 176 | export interface ContentTable { 177 | contentId: number; 178 | text: string; 179 | contentPosts: string; 180 | } 181 | 182 | // this table is for testing snakeName related stuff 183 | export function defineContentTable() { 184 | return Table.define({ 185 | name: 'content', 186 | columns: ['content_id', 'text', 'content_posts'], 187 | snakeToCamel: true 188 | }); 189 | } 190 | 191 | export { customTest as test }; 192 | -------------------------------------------------------------------------------- /test/dialects/ternary-clause-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const customer = Harness.defineCustomerTable(); 3 | const post = Harness.definePostTable(); 4 | 5 | Harness.test({ 6 | query: customer.select().where(customer.age.between(18, 25)), 7 | pg: { 8 | text: 'SELECT "customer".* FROM "customer" WHERE ("customer"."age" BETWEEN $1 AND $2)', 9 | string: 'SELECT "customer".* FROM "customer" WHERE ("customer"."age" BETWEEN 18 AND 25)' 10 | }, 11 | sqlite: { 12 | text: 'SELECT "customer".* FROM "customer" WHERE ("customer"."age" BETWEEN $1 AND $2)', 13 | string: 'SELECT "customer".* FROM "customer" WHERE ("customer"."age" BETWEEN 18 AND 25)' 14 | }, 15 | mysql: { 16 | text: 'SELECT `customer`.* FROM `customer` WHERE (`customer`.`age` BETWEEN ? AND ?)', 17 | string: 'SELECT `customer`.* FROM `customer` WHERE (`customer`.`age` BETWEEN 18 AND 25)' 18 | }, 19 | mssql: { 20 | text: 'SELECT [customer].* FROM [customer] WHERE ([customer].[age] BETWEEN @1 AND @2)', 21 | string: 'SELECT [customer].* FROM [customer] WHERE ([customer].[age] BETWEEN 18 AND 25)' 22 | }, 23 | oracle: { 24 | text: 'SELECT "customer".* FROM "customer" WHERE ("customer"."age" BETWEEN :1 AND :2)', 25 | string: 'SELECT "customer".* FROM "customer" WHERE ("customer"."age" BETWEEN 18 AND 25)' 26 | }, 27 | params: [18, 25] 28 | }); 29 | 30 | Harness.test({ 31 | query: post 32 | .select() 33 | .where(post.userId.between(customer.subQuery().select(customer.id.min()), customer.subQuery().select(customer.id.max()))), 34 | pg: { 35 | text: 36 | 'SELECT "post".* FROM "post" WHERE ("post"."userId" BETWEEN (SELECT MIN("customer"."id") AS "id_min" FROM "customer") AND (SELECT MAX("customer"."id") AS "id_max" FROM "customer"))', 37 | string: 38 | 'SELECT "post".* FROM "post" WHERE ("post"."userId" BETWEEN (SELECT MIN("customer"."id") AS "id_min" FROM "customer") AND (SELECT MAX("customer"."id") AS "id_max" FROM "customer"))' 39 | }, 40 | sqlite: { 41 | text: 42 | 'SELECT "post".* FROM "post" WHERE ("post"."userId" BETWEEN (SELECT MIN("customer"."id") AS "id_min" FROM "customer") AND (SELECT MAX("customer"."id") AS "id_max" FROM "customer"))', 43 | string: 44 | 'SELECT "post".* FROM "post" WHERE ("post"."userId" BETWEEN (SELECT MIN("customer"."id") AS "id_min" FROM "customer") AND (SELECT MAX("customer"."id") AS "id_max" FROM "customer"))' 45 | }, 46 | mysql: { 47 | text: 48 | 'SELECT `post`.* FROM `post` WHERE (`post`.`userId` BETWEEN (SELECT MIN(`customer`.`id`) AS `id_min` FROM `customer`) AND (SELECT MAX(`customer`.`id`) AS `id_max` FROM `customer`))', 49 | string: 50 | 'SELECT `post`.* FROM `post` WHERE (`post`.`userId` BETWEEN (SELECT MIN(`customer`.`id`) AS `id_min` FROM `customer`) AND (SELECT MAX(`customer`.`id`) AS `id_max` FROM `customer`))' 51 | }, 52 | mssql: { 53 | text: 54 | 'SELECT [post].* FROM [post] WHERE ([post].[userId] BETWEEN (SELECT MIN([customer].[id]) AS [id_min] FROM [customer]) AND (SELECT MAX([customer].[id]) AS [id_max] FROM [customer]))', 55 | string: 56 | 'SELECT [post].* FROM [post] WHERE ([post].[userId] BETWEEN (SELECT MIN([customer].[id]) AS [id_min] FROM [customer]) AND (SELECT MAX([customer].[id]) AS [id_max] FROM [customer]))' 57 | }, 58 | oracle: { 59 | text: 60 | 'SELECT "post".* FROM "post" WHERE ("post"."userId" BETWEEN (SELECT MIN("customer"."id") "id_min" FROM "customer") AND (SELECT MAX("customer"."id") "id_max" FROM "customer"))', 61 | string: 62 | 'SELECT "post".* FROM "post" WHERE ("post"."userId" BETWEEN (SELECT MIN("customer"."id") "id_min" FROM "customer") AND (SELECT MAX("customer"."id") "id_max" FROM "customer"))' 63 | }, 64 | params: [] 65 | }); 66 | -------------------------------------------------------------------------------- /test/dialects/tostring-tests.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import * as Harness from './support.js'; 3 | const post = Harness.definePostTable(); 4 | 5 | // Null 6 | Harness.test({ 7 | query: post.content.equals(null), 8 | pg: { 9 | text: '("post"."content" = $1)', 10 | string: '("post"."content" = NULL)' 11 | }, 12 | sqlite: { 13 | text: '("post"."content" = $1)', 14 | string: '("post"."content" = NULL)' 15 | }, 16 | mysql: { 17 | text: '(`post`.`content` = ?)', 18 | string: '(`post`.`content` = NULL)' 19 | }, 20 | mssql: { 21 | text: '([post].[content] = @1)', 22 | string: '([post].[content] = NULL)' 23 | }, 24 | oracle: { 25 | text: '("post"."content" = :1)', 26 | string: '("post"."content" = NULL)' 27 | }, 28 | params: [null] 29 | }); 30 | 31 | // Number 32 | Harness.test({ 33 | query: post.content.equals(3.14), 34 | pg: { 35 | text: '("post"."content" = $1)', 36 | string: '("post"."content" = 3.14)' 37 | }, 38 | sqlite: { 39 | text: '("post"."content" = $1)', 40 | string: '("post"."content" = 3.14)' 41 | }, 42 | mysql: { 43 | text: '(`post`.`content` = ?)', 44 | string: '(`post`.`content` = 3.14)' 45 | }, 46 | mssql: { 47 | text: '([post].[content] = @1)', 48 | string: '([post].[content] = 3.14)' 49 | }, 50 | oracle: { 51 | text: '("post"."content" = :1)', 52 | string: '("post"."content" = 3.14)' 53 | }, 54 | params: [3.14] 55 | }); 56 | 57 | // String 58 | Harness.test({ 59 | query: post.content.equals("hello'"), 60 | pg: { 61 | text: '("post"."content" = $1)', 62 | string: '("post"."content" = \'hello\'\'\')' 63 | }, 64 | sqlite: { 65 | text: '("post"."content" = $1)', 66 | string: '("post"."content" = \'hello\'\'\')' 67 | }, 68 | mysql: { 69 | text: '(`post`.`content` = ?)', 70 | string: "(`post`.`content` = 'hello''')" 71 | }, 72 | mssql: { 73 | text: '([post].[content] = @1)', 74 | string: "([post].[content] = 'hello''')" 75 | }, 76 | oracle: { 77 | text: '("post"."content" = :1)', 78 | string: '("post"."content" = \'hello\'\'\')' 79 | }, 80 | params: ["hello'"] 81 | }); 82 | 83 | // Array 84 | Harness.test({ 85 | query: post.content.equals([1, '2', null]), 86 | pg: { 87 | text: '("post"."content" = ($1, $2, $3))', 88 | string: '("post"."content" = (1, \'2\', NULL))' 89 | }, 90 | sqlite: { 91 | text: '("post"."content" = ($1, $2, $3))', 92 | string: '("post"."content" = (1, \'2\', NULL))' 93 | }, 94 | mysql: { 95 | text: '(`post`.`content` = (?, ?, ?))', 96 | string: "(`post`.`content` = (1, '2', NULL))" 97 | }, 98 | mssql: { 99 | text: 'SQL Server does not support arrays.', 100 | throws: true 101 | }, 102 | oracle: { 103 | text: 'SQL Server does not support arrays.', 104 | throws: true 105 | }, 106 | params: [1, '2', null] 107 | }); 108 | 109 | // Date 110 | Harness.test({ 111 | query: post.content.equals(new Date('Sat, 01 Jan 2000 00:00:00 GMT')), 112 | pg: { 113 | text: '("post"."content" = $1)', 114 | string: '("post"."content" = \'2000-01-01T00:00:00.000Z\')' 115 | }, 116 | sqlite: { 117 | text: '("post"."content" = $1)', 118 | string: '("post"."content" = \'2000-01-01T00:00:00.000Z\')' 119 | }, 120 | mysql: { 121 | text: '(`post`.`content` = ?)', 122 | string: "(`post`.`content` = '2000-01-01T00:00:00.000Z')" 123 | }, 124 | mssql: { 125 | text: '([post].[content] = @1)', 126 | string: "([post].[content] = '2000-01-01T00:00:00.000Z')" 127 | }, 128 | oracle: { 129 | text: '("post"."content" = :1)', 130 | string: '("post"."content" = \'2000-01-01T00:00:00.000Z\')' 131 | }, 132 | params: [new Date('Sat, 01 Jan 2000 00:00:00 GMT')] 133 | }); 134 | 135 | // Date to milliseconds 136 | Harness.test({ 137 | query: post.content.equals(new Date('Sat, 01 Jan 2000 00:00:00 GMT')), 138 | pg: { 139 | text: '("post"."content" = $1)', 140 | string: '("post"."content" = \'2000-01-01T00:00:00.000Z\')' 141 | }, 142 | sqlite: { 143 | text: '("post"."content" = $1)', 144 | string: '("post"."content" = 946684800000)', 145 | config: { 146 | dateTimeMillis: true 147 | } 148 | }, 149 | mysql: { 150 | text: '(`post`.`content` = ?)', 151 | string: "(`post`.`content` = '2000-01-01T00:00:00.000Z')" 152 | }, 153 | mssql: { 154 | text: '([post].[content] = @1)', 155 | string: "([post].[content] = '2000-01-01T00:00:00.000Z')" 156 | }, 157 | oracle: { 158 | text: '("post"."content" = :1)', 159 | string: '("post"."content" = \'2000-01-01T00:00:00.000Z\')' 160 | }, 161 | params: [new Date('Sat, 01 Jan 2000 00:00:00 GMT')] 162 | }); 163 | 164 | // Date Year 0 and before 165 | Harness.test({ 166 | query: post.content.equals(new Date('0000-01-01T00:00:00.000Z')), 167 | pg: { 168 | text: '("post"."content" = $1)', 169 | string: '("post"."content" = \'0001-01-01T00:00:00.000Z BC\')' 170 | }, 171 | sqlite: { 172 | text: '("post"."content" = $1)', 173 | string: '("post"."content" = \'0000-01-01T00:00:00.000Z\')' 174 | }, 175 | mysql: { 176 | text: '(`post`.`content` = ?)', 177 | string: "(`post`.`content` = '0000-01-01T00:00:00.000Z')" 178 | }, 179 | mssql: { 180 | text: '([post].[content] = @1)', 181 | string: "([post].[content] = '0000-01-01T00:00:00.000Z')" 182 | }, 183 | oracle: { 184 | text: '("post"."content" = :1)', 185 | string: '("post"."content" = \'0000-01-01T00:00:00.000Z\')' 186 | }, 187 | params: [new Date('0000-01-01T00:00:00.000Z')] 188 | }); 189 | 190 | // Object 191 | const customObject = { 192 | toString() { 193 | return 'secretMessage'; 194 | } 195 | }; 196 | 197 | Harness.test({ 198 | query: post.content.equals(customObject), 199 | pg: { 200 | text: '("post"."content" = $1)', 201 | string: '("post"."content" = \'secretMessage\')' 202 | }, 203 | sqlite: { 204 | text: '("post"."content" = $1)', 205 | string: '("post"."content" = \'secretMessage\')' 206 | }, 207 | mysql: { 208 | text: '(`post`.`content` = ?)', 209 | string: "(`post`.`content` = 'secretMessage')" 210 | }, 211 | mssql: { 212 | text: '([post].[content] = @1)', 213 | string: "([post].[content] = 'secretMessage')" 214 | }, 215 | oracle: { 216 | text: '("post"."content" = :1)', 217 | string: '("post"."content" = \'secretMessage\')' 218 | }, 219 | params: [customObject] 220 | }); 221 | 222 | // Undefined 223 | assert.throws(function() { 224 | post.content.equals(undefined).toString(); 225 | }); 226 | -------------------------------------------------------------------------------- /test/dialects/truncate-table-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const post = Harness.definePostTable(); 3 | 4 | Harness.test({ 5 | query: post.truncate(), 6 | pg: { 7 | text: 'TRUNCATE TABLE "post"', 8 | string: 'TRUNCATE TABLE "post"' 9 | }, 10 | sqlite: { 11 | text: 'DELETE FROM "post"', 12 | string: 'DELETE FROM "post"' 13 | }, 14 | mysql: { 15 | text: 'TRUNCATE TABLE `post`', 16 | string: 'TRUNCATE TABLE `post`' 17 | }, 18 | mssql: { 19 | text: 'TRUNCATE TABLE [post]', 20 | string: 'TRUNCATE TABLE [post]' 21 | }, 22 | oracle: { 23 | text: 'TRUNCATE TABLE "post"', 24 | string: 'TRUNCATE TABLE "post"' 25 | }, 26 | params: [] 27 | }); 28 | -------------------------------------------------------------------------------- /test/dialects/unary-clause-tests.ts: -------------------------------------------------------------------------------- 1 | import * as Harness from './support.js'; 2 | const customer = Harness.defineCustomerTable(); 3 | const post = Harness.definePostTable(); 4 | 5 | Harness.test({ 6 | query: customer.select().where(customer.age.isNotNull()), 7 | pg: { 8 | text: 'SELECT "customer".* FROM "customer" WHERE ("customer"."age" IS NOT NULL)', 9 | string: 'SELECT "customer".* FROM "customer" WHERE ("customer"."age" IS NOT NULL)' 10 | }, 11 | sqlite: { 12 | text: 'SELECT "customer".* FROM "customer" WHERE ("customer"."age" IS NOT NULL)', 13 | string: 'SELECT "customer".* FROM "customer" WHERE ("customer"."age" IS NOT NULL)' 14 | }, 15 | mysql: { 16 | text: 'SELECT `customer`.* FROM `customer` WHERE (`customer`.`age` IS NOT NULL)', 17 | string: 'SELECT `customer`.* FROM `customer` WHERE (`customer`.`age` IS NOT NULL)' 18 | }, 19 | mssql: { 20 | text: 'SELECT [customer].* FROM [customer] WHERE ([customer].[age] IS NOT NULL)', 21 | string: 'SELECT [customer].* FROM [customer] WHERE ([customer].[age] IS NOT NULL)' 22 | }, 23 | oracle: { 24 | text: 'SELECT "customer".* FROM "customer" WHERE ("customer"."age" IS NOT NULL)', 25 | string: 'SELECT "customer".* FROM "customer" WHERE ("customer"."age" IS NOT NULL)' 26 | }, 27 | params: [] 28 | }); 29 | 30 | Harness.test({ 31 | query: post.select().where( 32 | post.userId.in( 33 | customer 34 | .subQuery() 35 | .select(customer.id) 36 | .where(customer.age.isNull()) 37 | ) 38 | ), 39 | pg: { 40 | text: 41 | 'SELECT "post".* FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer" WHERE ("customer"."age" IS NULL)))', 42 | string: 43 | 'SELECT "post".* FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer" WHERE ("customer"."age" IS NULL)))' 44 | }, 45 | sqlite: { 46 | text: 47 | 'SELECT "post".* FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer" WHERE ("customer"."age" IS NULL)))', 48 | string: 49 | 'SELECT "post".* FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer" WHERE ("customer"."age" IS NULL)))' 50 | }, 51 | mysql: { 52 | text: 53 | 'SELECT `post`.* FROM `post` WHERE (`post`.`userId` IN (SELECT `customer`.`id` FROM `customer` WHERE (`customer`.`age` IS NULL)))', 54 | string: 55 | 'SELECT `post`.* FROM `post` WHERE (`post`.`userId` IN (SELECT `customer`.`id` FROM `customer` WHERE (`customer`.`age` IS NULL)))' 56 | }, 57 | mssql: { 58 | text: 59 | 'SELECT [post].* FROM [post] WHERE ([post].[userId] IN (SELECT [customer].[id] FROM [customer] WHERE ([customer].[age] IS NULL)))', 60 | string: 61 | 'SELECT [post].* FROM [post] WHERE ([post].[userId] IN (SELECT [customer].[id] FROM [customer] WHERE ([customer].[age] IS NULL)))' 62 | }, 63 | oracle: { 64 | text: 65 | 'SELECT "post".* FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer" WHERE ("customer"."age" IS NULL)))', 66 | string: 67 | 'SELECT "post".* FROM "post" WHERE ("post"."userId" IN (SELECT "customer"."id" FROM "customer" WHERE ("customer"."age" IS NULL)))' 68 | }, 69 | params: [] 70 | }); 71 | -------------------------------------------------------------------------------- /test/function-tests.ts: -------------------------------------------------------------------------------- 1 | import { strictEqual } from 'assert'; 2 | 3 | import { Sql } from '../dist/lib.js'; 4 | const instance = new Sql('postgres'); 5 | 6 | const user = instance.define<{ id: string; email: string; name: string; howOld: number }>({ 7 | name: 'user', 8 | columns: [{ name: 'id' }, { name: 'email' }, { name: 'name' }, { name: 'age', property: 'howOld' }] 9 | }); 10 | 11 | suite('function', function() { 12 | test('alias function call', function() { 13 | const upper = instance.functions.UPPER; 14 | const aliasedUpper = upper(user.email) 15 | .as('upperAlias') 16 | .toQuery(); 17 | 18 | strictEqual(aliasedUpper.text, 'UPPER("user"."email") AS "upperAlias"'); 19 | }); 20 | 21 | test('function call on aliased column', function() { 22 | const round = instance.functions.ROUND; 23 | const aliasedRound = round(user.howOld, 2).toQuery(); 24 | 25 | strictEqual(aliasedRound.text, 'ROUND("user"."age", $1)'); 26 | strictEqual(aliasedRound.values[0], 2); 27 | }); 28 | 29 | test('creating function call works', function() { 30 | const upper = instance.function('UPPER'); 31 | const functionCall = upper('hello', 'world').toQuery(); 32 | 33 | strictEqual(functionCall.text, 'UPPER($1, $2)'); 34 | strictEqual(functionCall.values[0], 'hello'); 35 | strictEqual(functionCall.values[1], 'world'); 36 | }); 37 | 38 | test('creating function call on columns works', function() { 39 | const upper = instance.function('UPPER'); 40 | const functionCall = upper(user.id, user.email).toQuery(); 41 | 42 | strictEqual(functionCall.text, 'UPPER("user"."id", "user"."email")'); 43 | strictEqual(functionCall.values.length, 0); 44 | }); 45 | 46 | test('function call inside select works', function() { 47 | const upper = instance.function('UPPER'); 48 | const query = instance 49 | .select(upper(user.id, user.email)) 50 | .from(user) 51 | .where(user.email.equals('brian.m.carlson@gmail.com')) 52 | .toQuery(); 53 | 54 | strictEqual(query.text, 'SELECT UPPER("user"."id", "user"."email") FROM "user" WHERE ("user"."email" = $1)'); 55 | strictEqual(query.values[0], 'brian.m.carlson@gmail.com'); 56 | }); 57 | 58 | test('standard aggregate functions with having clause', function() { 59 | const count = instance.functions.COUNT; 60 | const distinct = instance.functions.DISTINCT; 61 | const distinctEmailCount = count(distinct(user.email)); 62 | 63 | const query = user 64 | .select(user.id, distinctEmailCount) 65 | .group(user.id) 66 | .having(distinctEmailCount.gt(100)) 67 | .toQuery(); 68 | 69 | strictEqual( 70 | query.text, 71 | 'SELECT "user"."id", COUNT(DISTINCT("user"."email")) FROM "user" GROUP BY "user"."id" HAVING (COUNT(DISTINCT("user"."email")) > $1)' 72 | ); 73 | strictEqual(query.values[0], 100); 74 | }); 75 | 76 | test('custom and standard functions behave the same', function() { 77 | const standardUpper = instance.functions.UPPER; 78 | const customUpper = instance.function('UPPER'); 79 | 80 | const standardQuery = user.select(standardUpper(user.name)).toQuery(); 81 | const customQuery = user.select(customUpper(user.name)).toQuery(); 82 | 83 | const expectedQuery = 'SELECT UPPER("user"."name") FROM "user"'; 84 | strictEqual(standardQuery.text, expectedQuery); 85 | strictEqual(customQuery.text, expectedQuery); 86 | }); 87 | 88 | test('combine function with operations', function() { 89 | const f = instance.functions; 90 | const query = user.select(f.AVG(f.DISTINCT(f.COUNT(user.id).plus(f.MAX(user.id))).minus(f.MIN(user.id))).multiply(100)).toQuery(); 91 | 92 | strictEqual(query.text, 'SELECT (AVG((DISTINCT((COUNT("user"."id") + MAX("user"."id"))) - MIN("user"."id"))) * $1) FROM "user"'); 93 | strictEqual(query.values[0], 100); 94 | }); 95 | 96 | test('use custom function', function() { 97 | const query = user.select(instance.function('PHRASE_TO_TSQUERY')('simple', user.name)).toQuery(); 98 | strictEqual(query.text, 'SELECT PHRASE_TO_TSQUERY($1, "user"."name") FROM "user"'); 99 | strictEqual(query.values[0], 'simple'); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /test/operator-tests.ts: -------------------------------------------------------------------------------- 1 | import { strictEqual } from 'assert'; 2 | 3 | import { Sql } from '../dist/lib.js'; 4 | const instance = new Sql('postgres'); 5 | 6 | const user = instance.define<{ id: string; email: string; name: string; howOld: number }>({ 7 | name: 'user', 8 | columns: [{ name: 'id' }, { name: 'email' }, { name: 'name' }, { name: 'age', property: 'howOld' }] 9 | }); 10 | 11 | suite('operator', function() { 12 | // PREFIX UNARY 13 | test('creating a prefix unary operator works', function() { 14 | const not = instance.prefixUnaryOperator('NOT'); 15 | const operator = not(user.id).toQuery(); 16 | 17 | strictEqual(operator.text, '(NOT "user"."id")'); 18 | }); 19 | 20 | test('creating a prefix unary operator works with a parameter', function() { 21 | const not = instance.prefixUnaryOperator('NOT'); 22 | const operator = not('hello').toQuery(); 23 | 24 | strictEqual(operator.text, '(NOT $1)'); 25 | strictEqual(operator.values[0], 'hello'); 26 | }); 27 | 28 | test('creating a prefix unary operator works via the mixin', function() { 29 | const operator = user.id.prefixUnaryOperator('NOT').toQuery(); 30 | 31 | strictEqual(operator.text, '(NOT "user"."id")'); 32 | }); 33 | 34 | // POSTFIX UNARY 35 | test('creating a postfix unary operator works', function() { 36 | const isNull = instance.postfixUnaryOperator('IS NULL'); 37 | const operator = isNull(user.id).toQuery(); 38 | 39 | strictEqual(operator.text, '("user"."id" IS NULL)'); 40 | }); 41 | 42 | test('creating a postfix unary operator works with a parameter', function() { 43 | const isNull = instance.postfixUnaryOperator('IS NULL'); 44 | const operator = isNull('hello').toQuery(); 45 | 46 | strictEqual(operator.text, '($1 IS NULL)'); 47 | strictEqual(operator.values[0], 'hello'); 48 | }); 49 | 50 | test('creating a postfix unary operator works via the mixin', function() { 51 | const operator = user.id.postfixUnaryOperator('IS NOT NULL').toQuery(); 52 | 53 | strictEqual(operator.text, '("user"."id" IS NOT NULL)'); 54 | }); 55 | 56 | // BINARY 57 | test('creating a binary operator works', function() { 58 | const lt = instance.binaryOperator('<'); 59 | const operator = lt(user.id, 3).toQuery(); 60 | 61 | strictEqual(operator.text, '("user"."id" < $1)'); 62 | strictEqual(operator.values[0], 3); 63 | }); 64 | 65 | test('creating a binary operator works via the mixin', function() { 66 | const operator = user.id.binaryOperator('<->', 3).toQuery(); 67 | 68 | strictEqual(operator.text, '("user"."id" <-> $1)'); 69 | strictEqual(operator.values[0], 3); 70 | }); 71 | 72 | // TERNARY 73 | test('creating a ternary operator works', function() { 74 | const between = instance.ternaryOperator('BETWEEN', 'AND'); 75 | const operator = between(user.id, 3, user.id).toQuery(); 76 | 77 | strictEqual(operator.text, '("user"."id" BETWEEN $1 AND "user"."id")'); 78 | strictEqual(operator.values[0], 3); 79 | }); 80 | 81 | test('creating a ternary operator works via the mixin', function() { 82 | const operator = user.id.ternaryOperator('BETWEEN', 3, 'AND', user.id).toQuery(); 83 | 84 | strictEqual(operator.text, '("user"."id" BETWEEN $1 AND "user"."id")'); 85 | strictEqual(operator.values[0], 3); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/select-tests.ts: -------------------------------------------------------------------------------- 1 | import { strictEqual } from 'assert'; 2 | import { SelectNode } from '../dist/lib.js'; 3 | 4 | const select = new SelectNode(); 5 | 6 | test('has SELECT type', function() { 7 | strictEqual(select.type, 'SELECT'); 8 | }); 9 | -------------------------------------------------------------------------------- /test/ternary-clause-tests.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { Table } from '../dist/lib.js'; 3 | 4 | const Foo = Table.define<{ baz: string; bar: string }>({ 5 | name: 'foo', 6 | columns: ['baz', 'bar'] 7 | }); 8 | 9 | test('operators', function() { 10 | assert.strictEqual(Foo.bar.between(1, 2).operator, 'BETWEEN'); 11 | assert.strictEqual(Foo.baz.between(1, 2).separator, 'AND'); 12 | }); 13 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../spec" 5 | }, 6 | "include": ["./*.ts", "./**/*.ts", "./**/*.js", "./**/*.d.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /test/unary-clause-tests.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { Table } from '../dist/lib.js'; 3 | 4 | const Foo = Table.define<{ baz: string; bar: string }>({ 5 | name: 'foo', 6 | columns: ['baz', 'bar'] 7 | }); 8 | 9 | test('operators', function() { 10 | assert.strictEqual(Foo.bar.isNull().operator, 'IS NULL'); 11 | assert.strictEqual(Foo.baz.isNotNull().operator, 'IS NOT NULL'); 12 | }); 13 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./lib", 5 | "outDir": "./dist", 6 | "declarationDir": "./dts" 7 | }, 8 | "include": ["./lib/**/*.ts"] 9 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "esModuleInterop": true, 8 | "noEmitHelpers": true, 9 | "importHelpers": true, 10 | "pretty": true, 11 | "sourceMap": true, 12 | "lib": ["es2022", "esnext.asynciterable"], 13 | "strict": true, 14 | "emitDecoratorMetadata": true, 15 | "experimentalDecorators": true, 16 | "removeComments": false, 17 | "noImplicitReturns": true, 18 | "declaration": true, 19 | "typeRoots": ["@types", "node_modules/@types"] 20 | }, 21 | "exclude": ["node_modules", "./dist"], 22 | "compileOnSave": true, 23 | "buildOnSave": true 24 | } 25 | --------------------------------------------------------------------------------