├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ ├── release-alpha.yml │ ├── release-beta.yml │ ├── release-v1.yml │ ├── release.yml │ └── test-release.yml ├── .gitignore ├── .npmignore ├── .readthedocs.yaml ├── .vscode └── schema.json ├── LICENSE.md ├── README.md ├── docs ├── CHANGELOG.md ├── README.md ├── about │ ├── examples.md │ ├── faqs.md │ ├── limimitations.md │ └── philosophy.md ├── advanced │ ├── columns-from-object.md │ ├── custom-booleans-values.md │ ├── id-manipulation.md │ ├── query-execution-metadata.md │ ├── synchronous-query-runners.md │ ├── tables-views-as-parameter.md │ ├── utility-dynamic-picks.md │ └── utility-types.md ├── api │ ├── connection.md │ ├── constant-values-view.md │ ├── delete.md │ ├── dynamic-conditions.md │ ├── insert.md │ ├── introduction.md │ ├── select.md │ ├── table.md │ ├── type-adpaters.md │ ├── update.md │ ├── value-expressions.md │ └── view.md ├── configuration │ ├── column-types.md │ ├── connection.md │ ├── mapping.md │ ├── query-runners │ │ ├── additional │ │ │ ├── mysql.md │ │ │ ├── prisma.md │ │ │ └── sqlite.md │ │ ├── general-purpose │ │ │ ├── ConsoleLogNoopQueryRunner.md │ │ │ ├── ConsoleLogQueryRunner.md │ │ │ ├── InterceptorQueryRunner.md │ │ │ ├── LoggingQueryRunner.md │ │ │ ├── MockQueryRunner.md │ │ │ └── NoopQueryRunner.md │ │ └── recommended │ │ │ ├── better-sqlite3.md │ │ │ ├── mariadb.md │ │ │ ├── mssql.md │ │ │ ├── mysql2.md │ │ │ ├── oracledb.md │ │ │ ├── pg.md │ │ │ ├── postgres.md │ │ │ ├── sqlite-wasm-OO1.md │ │ │ └── sqlite3.md │ └── supported-databases │ │ ├── mariadb.md │ │ ├── mysql.md │ │ ├── oracle.md │ │ ├── postgresql.md │ │ ├── sqlite.md │ │ └── sqlserver.md ├── demo.gif ├── extra.css ├── index.md ├── keywords │ ├── delete.md │ ├── functions-oprators.md │ ├── insert.md │ ├── select.md │ ├── transaction.md │ └── update.md ├── logo.svg ├── queries │ ├── aggregate-as-object-array.md │ ├── basic-query-structure.md │ ├── complex-projections.md │ ├── delete.md │ ├── dynamic-queries.md │ ├── extreme-dynamic-queries.md │ ├── insert.md │ ├── recursive-select.md │ ├── select-page.md │ ├── select.md │ ├── sql-fragments.md │ ├── transaction.md │ └── update.md ├── requirements.txt └── select.pptx ├── mkdocs.yml ├── package.json ├── scripts ├── create-single-doc-file.sh ├── run-all-examples-arm.sh ├── run-all-examples-rosetta.sh ├── run-all-examples.sh └── run-no-docker-examples.sh ├── src ├── Connection.ts ├── Table.ts ├── TypeAdapter.ts ├── Values.ts ├── View.ts ├── complexProjections │ ├── asLeftJoin.ts │ ├── asWithView.ts │ ├── compound.ts │ ├── dataToProject.ts │ ├── picking.ts │ ├── projectionRules.ts │ ├── resultWithOptionalsAsNull.ts │ ├── resultWithOptionalsAsUndefined.ts │ └── tableAlias.ts ├── connections │ ├── AbstractAdvancedConnection.ts │ ├── AbstractConnection.ts │ ├── MariaDBConnection.ts │ ├── MySqlConnection.ts │ ├── NoopDBConnection.ts │ ├── OracleConnection.ts │ ├── PostgreSqlConnection.ts │ ├── SqlServerConnection.ts │ ├── SqliteConfiguration.ts │ └── SqliteConnection.ts ├── dynamicCondition.ts ├── examples │ ├── BetterSqlite3Example.ts │ ├── BetterSqlite3SynchronousExample.ts │ ├── EncriptedIDPgExample.ts │ ├── MariaDBExample-modern.ts │ ├── MariaDBExample.ts │ ├── MssqlTediousExample.ts │ ├── MySql2Example.ts │ ├── MySqlExample.ts │ ├── OracleDBExample.ts │ ├── PgExample.ts │ ├── PostgresExample.ts │ ├── PrismaMariaDBExample.ts │ ├── PrismaMySqlExample.ts │ ├── PrismaPostgresExample.ts │ ├── PrismaSqlServerExample.ts │ ├── PrismaSqliteExample.ts │ ├── Sqlite3Example.ts │ ├── Sqlite3WasmOO1Example.ts │ ├── Sqlite3WasmOO1SynchronousExample.ts │ ├── SqliteExample.ts │ ├── assertEquals.ts │ ├── documentation │ │ ├── MariaDB-modern.ts │ │ ├── MariaDB.ts │ │ ├── MySql-compatibility.ts │ │ ├── MySql.ts │ │ ├── Oracle.ts │ │ ├── PostgreSql.ts │ │ ├── SqlServer.ts │ │ ├── Sqlite-compatibility.ts │ │ └── Sqlite-modern.ts │ └── prisma │ │ ├── mariadb.prisma │ │ ├── mysql.prisma │ │ ├── postgresql.prisma │ │ ├── sqlite.prisma │ │ └── sqlserver.prisma ├── expressions │ ├── Default.ts │ ├── delete.ts │ ├── dynamicConditionUsingFilters.ts │ ├── fragment.ts │ ├── insert.ts │ ├── select.ts │ ├── sequence.ts │ ├── update.ts │ └── values.ts ├── extras │ ├── IDEncrypter.ts │ ├── types.ts │ └── utils.ts ├── internal │ ├── DBColumnImpl.ts │ ├── ProxyTypeAdapter.ts │ ├── RawFragmentImpl.ts │ ├── ValueSourceImpl.ts │ └── WithViewImpl.ts ├── queryBuilders │ ├── AbstractQueryBuilder.ts │ ├── DeleteQueryBuilder.ts │ ├── DynamicConditionBuilder.ts │ ├── FragmentQueryBuilder.ts │ ├── InsertQueryBuilder.ts │ ├── SelectQueryBuilder.ts │ ├── SequenceQueryBuilder.ts │ └── UpdateQueryBuilder.ts ├── queryRunners │ ├── AbstractPoolQueryRunner.ts │ ├── AbstractQueryRunner.ts │ ├── BetterSqlite3QueryRunner.ts │ ├── ChainedQueryRunner.ts │ ├── ConsoleLogNoopQueryRunner.ts │ ├── ConsoleLogQueryRunner.ts │ ├── DelegatedSetTransactionQueryRunner.ts │ ├── InterceptorQueryRunner.ts │ ├── LoggingQueryRunner.ts │ ├── ManagedTransactionPoolQueryRunner.ts │ ├── ManagedTransactionQueryRunner.ts │ ├── MariaDBPoolQueryRunner.ts │ ├── MariaDBQueryRunner.ts │ ├── MockQueryRunner.ts │ ├── MssqlPoolPromiseQueryRunner.ts │ ├── MssqlPoolQueryRunner.ts │ ├── MySql2PoolQueryRunner.ts │ ├── MySql2QueryRunner.ts │ ├── MySqlPoolQueryRunner.ts │ ├── MySqlQueryRunner.ts │ ├── NoopQueryRunner.ts │ ├── OracleDBPoolPromiseQueryRunner.ts │ ├── OracleDBPoolQueryRunner.ts │ ├── OracleDBQueryRunner.ts │ ├── PgPoolQueryRunner.ts │ ├── PgQueryRunner.ts │ ├── PostgresQueryRunner.ts │ ├── PrismaQueryRunner.ts │ ├── QueryRunner.ts │ ├── SqlTransactionQueryRunner.ts │ ├── Sqlite3QueryRunner.ts │ ├── Sqlite3WasmOO1QueryRunner.ts │ └── SqliteQueryRunner.ts ├── simplifiedDefinition.txt ├── sqlBuilders │ ├── AbstractMySqlMariaBDSqlBuilder.ts │ ├── AbstractSqlBuilder.ts │ ├── MariaDBSqlBuilder.ts │ ├── MySqlSqlBuilder.ts │ ├── NoopDBSqlBuilder.ts │ ├── OracleSqlBuilder.ts │ ├── PostgreSqlSqlBuilder.ts │ ├── SqlBuilder.ts │ ├── SqlServerSqlBuilder.ts │ └── SqliteSqlBuilder.ts └── utils │ ├── Column.ts │ ├── ConnectionConfiguration.ts │ ├── IConnection.ts │ ├── ITableOrView.ts │ ├── PromiseUtils.ts │ ├── RawFragment.ts │ ├── attachSource.ts │ ├── leftJoinUtils.ts │ ├── objectUtils.ts │ ├── resultUtils.ts │ ├── sourceName.ts │ ├── symbols.ts │ └── tableOrViewUtils.ts ├── test └── README.md └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # For more information about the properties used in 2 | # this file, please see the EditorConfig documentation: 3 | # http://editorconfig.org/ 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 4 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: juanluispaz 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [master, v1] 4 | pull_request: 5 | 6 | name: CI 7 | 8 | jobs: 9 | build: 10 | name: build library 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node-version: [18.x, 20.x] 15 | steps: 16 | # Setup environment 17 | - uses: actions/checkout@v4 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | 23 | # Install dependencies 24 | - name: Install dependencies 25 | run: npm install 26 | 27 | # Build 28 | - name: Prepare 29 | run: npm run generate-prisma 30 | - name: Build 31 | run: npm run build 32 | 33 | # Light tests (docker tests not supported yet) 34 | - name: Light tests 35 | run: npm run no-docker-examples 36 | -------------------------------------------------------------------------------- /.github/workflows/release-alpha.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | 4 | name: Release-Alpha 5 | 6 | jobs: 7 | build: 8 | name: build library 9 | runs-on: ubuntu-latest 10 | environment: npm-release 11 | 12 | steps: 13 | # Setup environment 14 | - uses: actions/checkout@v4 15 | - name: Use Node.js 20.x 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: '20.x' 19 | registry-url: 'https://registry.npmjs.org' 20 | 21 | # Validate branch 22 | - name: Validate if in master branch 23 | run: '[ "$GITHUB_REF_NAME" == "master" ] || { echo "Not in master: $GITHUB_REF_NAME"; exit 1; }' 24 | 25 | # Install dependencies 26 | - name: Install dependencies 27 | run: npm install 28 | 29 | # Build 30 | - name: Prepare 31 | run: npm run generate-prisma 32 | - name: Build 33 | run: npm run build 34 | 35 | # Light tests (docker tests not supported yet) 36 | - name: Light tests 37 | run: npm run no-docker-examples 38 | 39 | # Publish 40 | - name: Build for dist & Publish 41 | run: npm run dist-alpha 42 | env: 43 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 44 | 45 | -------------------------------------------------------------------------------- /.github/workflows/release-beta.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | 4 | name: Release-Beta 5 | 6 | jobs: 7 | build: 8 | name: build library 9 | runs-on: ubuntu-latest 10 | environment: npm-release 11 | 12 | steps: 13 | # Setup environment 14 | - uses: actions/checkout@v4 15 | - name: Use Node.js 20.x 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: '20.x' 19 | registry-url: 'https://registry.npmjs.org' 20 | 21 | # Validate branch 22 | - name: Validate if in master branch 23 | run: '[ "$GITHUB_REF_NAME" == "master" ] || { echo "Not in master: $GITHUB_REF_NAME"; exit 1; }' 24 | 25 | # Install dependencies 26 | - name: Install dependencies 27 | run: npm install 28 | 29 | # Build 30 | - name: Prepare 31 | run: npm run generate-prisma 32 | - name: Build 33 | run: npm run build 34 | 35 | # Light tests (docker tests not supported yet) 36 | - name: Light tests 37 | run: npm run no-docker-examples 38 | 39 | # Publish 40 | - name: Build for dist & Publish 41 | run: npm run dist-beta 42 | env: 43 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 44 | 45 | -------------------------------------------------------------------------------- /.github/workflows/release-v1.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | 4 | name: Release-V1 5 | 6 | jobs: 7 | build: 8 | name: build library 9 | runs-on: ubuntu-latest 10 | environment: npm-release 11 | 12 | steps: 13 | # Setup environment 14 | - uses: actions/checkout@v4 15 | with: 16 | ref: v1 17 | 18 | - name: Use Node.js 20.x 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: '20.x' 22 | registry-url: 'https://registry.npmjs.org' 23 | 24 | # Show package.json 25 | - name: Show package.json 26 | run: 'cat package.json' 27 | 28 | # Install dependencies 29 | - name: Install dependencies 30 | run: npm install 31 | 32 | # Build 33 | - name: Prepare 34 | run: npm run generate-prisma 35 | - name: Build 36 | run: npm run build 37 | 38 | # Light tests (docker tests not supported yet) 39 | - name: Light tests 40 | run: npm run no-docker-examples 41 | 42 | # Publish 43 | - name: Build for dist & Publish 44 | run: npm run dist 45 | env: 46 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 47 | 48 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | 4 | name: Release 5 | 6 | jobs: 7 | build: 8 | name: build library 9 | runs-on: ubuntu-latest 10 | environment: npm-release 11 | 12 | steps: 13 | # Setup environment 14 | - uses: actions/checkout@v4 15 | - name: Use Node.js 20.x 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: '20.x' 19 | registry-url: 'https://registry.npmjs.org' 20 | 21 | # Validate branch 22 | - name: Validate if in master branch 23 | run: '[ "$GITHUB_REF_NAME" == "master" ] || { echo "Not in master: $GITHUB_REF_NAME"; exit 1; }' 24 | 25 | # Install dependencies 26 | - name: Install dependencies 27 | run: npm install 28 | 29 | # Build 30 | - name: Prepare 31 | run: npm run generate-prisma 32 | - name: Build 33 | run: npm run build 34 | 35 | # Light tests (docker tests not supported yet) 36 | - name: Light tests 37 | run: npm run no-docker-examples 38 | 39 | # Publish 40 | - name: Build for dist & Publish 41 | run: npm run dist 42 | env: 43 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 44 | 45 | -------------------------------------------------------------------------------- /.github/workflows/test-release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | 4 | name: Test-Release 5 | 6 | jobs: 7 | build: 8 | name: build library 9 | runs-on: ubuntu-latest 10 | environment: npm-release 11 | 12 | steps: 13 | # Setup environment 14 | - uses: actions/checkout@v4 15 | - name: Use Node.js 20.x 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: '20.x' 19 | registry-url: 'https://registry.npmjs.org' 20 | 21 | # Validate branch 22 | - name: Validate if in master branch 23 | run: '[ "$GITHUB_REF_NAME" == "master" ] || { echo "Not in master: $GITHUB_REF_NAME"; exit 1; }' 24 | 25 | # Install dependencies 26 | - name: Install dependencies 27 | run: npm install 28 | 29 | # Build 30 | - name: Prepare 31 | run: npm run generate-prisma 32 | - name: Build 33 | run: npm run build 34 | 35 | # Light tests (docker tests not supported yet) 36 | - name: Light tests 37 | run: npm run no-docker-examples 38 | 39 | # Publish 40 | - name: Build for dist & Publish 41 | run: npm run test-dist 42 | env: 43 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 44 | 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Tt]humbs.db 2 | .DS_Store 3 | node_modules 4 | npm-debug.log* 5 | coverage 6 | package-lock.json 7 | dist 8 | generated 9 | .github 10 | .nyc_output 11 | .venv -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | [Tt]humbs.db 2 | .DS_Store 3 | node_modules 4 | npm-debug.log* 5 | coverage 6 | package-lock.json 7 | test 8 | .travis.yml 9 | src 10 | tsconfig.json 11 | generated 12 | .github 13 | .nyc_output 14 | .venv 15 | .vscode -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | mkdocs: 4 | configuration: mkdocs.yml 5 | 6 | build: 7 | os: ubuntu-22.04 8 | tools: 9 | python: "3.12" 10 | 11 | python: 12 | install: 13 | - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /.vscode/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "yaml.schemas": { 3 | "https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml" 4 | }, 5 | "yaml.customTags": [ 6 | "!ENV scalar", 7 | "!ENV sequence", 8 | "!relative scalar", 9 | "tag:yaml.org,2002:python/name:material.extensions.emoji.to_svg", 10 | "tag:yaml.org,2002:python/name:material.extensions.emoji.twemoji", 11 | "tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format", 12 | "tag:yaml.org,2002:python/object/apply:pymdownx.slugs.slugify mapping" 13 | ] 14 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Juan Luis Paz Rojas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # ts-sql-query documentation 2 | 3 | In this folder, you will find all the documentation of `ts-sql-query`. 4 | 5 | This documentation is built using [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) 9.6.11. To see a local version, you must have installed mkdocs and execute the command: 6 | 7 | ```sh 8 | $ npm run docs 9 | ``` 10 | 11 | You can read the latest version of the documentation at: https://ts-sql-query.readthedocs.io/ 12 | -------------------------------------------------------------------------------- /docs/about/examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | exclude: true 4 | --- 5 | # Examples 6 | 7 | You can find a complete example using `ts-sql-query` with [PostgreSQL](../configuration/supported-databases/postgresql.md) in the file [PgExample.ts](https://github.com/juanluispaz/ts-sql-query/blob/master/src/examples/PgExample.ts). You can browse the [examples folder](https://github.com/juanluispaz/ts-sql-query/tree/master/src/examples) to see an example for each supported database using different ways to connect to it. 8 | 9 | **Running examples:** 10 | 11 | The first time you download the project: 12 | 13 | ```sh 14 | npm install 15 | npm run generate-prisma 16 | ``` 17 | 18 | To execute all examples: 19 | 20 | ```sh 21 | npm run all-examples 22 | ``` 23 | 24 | This command will compile the project and execute the script located at [scripts/run-all-examples.sh](https://github.com/juanluispaz/ts-sql-query/blob/master/scripts/run-all-examples.sh) 25 | 26 | !!! note "Be aware" 27 | 28 | This command expects you to have docker running and [Oracle](../configuration/supported-databases/oracle.md) instantclient-basic downloaded and configured the path in the script (see the script to get more details) 29 | 30 | If you want to execute a single example (like [SQLite](../configuration/supported-databases/sqlite.md)): 31 | 32 | ```sh 33 | npx ts-node ./src/examples/SqliteExample.ts 34 | ``` 35 | 36 | !!! note "Be aware" 37 | 38 | All examples excepting [SQLite](../configuration/supported-databases/sqlite.md) require a specific docker image with the database running; see [scripts/run-all-examples.sh](https://github.com/juanluispaz/ts-sql-query/blob/master/scripts/run-all-examples.sh) for more details. 39 | -------------------------------------------------------------------------------- /docs/advanced/custom-booleans-values.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.7 4 | --- 5 | # Custom booleans values 6 | 7 | Sometimes, especially when working with [Oracle](../configuration/supported-databases/oracle.md) databases, booleans are stored using custom values rather than standard `true` and `false`. For instance, a field might use the character `'Y'` to represent `true` and `'N'` to represent `false`. 8 | 9 | For example: 10 | 11 | ```ts 12 | import { Table } from "ts-sql-query/Table"; 13 | import { CustomBooleanTypeAdapter } from "ts-sql-query/TypeAdapter"; 14 | 15 | const tCustomCompany = new class TCustomCompany extends Table { 16 | id = this.autogeneratedPrimaryKey('id', 'int'); 17 | name = this.column('name', 'string'); 18 | isBig = this.column('is_big', 'boolean', new CustomBooleanTypeAdapter('Y', 'N')); 19 | constructor() { 20 | super('custom_company'); // table name in the database 21 | } 22 | }(); 23 | ``` 24 | 25 | The table `custom_company` the field `is_big` accepts the values `Y` and `N`. This field represents a boolean type, and on the JavaScript side, it will be mapped as boolean. But, on the database side, the field will be treated with appropriated values. The conversion between values will be performed by `ts-sql-query` automatically; you don't need to be worried about the type mismatching even if you try to assign the value to another field with a different way of representing booleans. 26 | 27 | You can perform an insert in this way: 28 | 29 | ```ts 30 | const insertCustomCompany = connection.insertInto(tCustomCompany).set({ 31 | name: 'My Big Company', 32 | isBig: true 33 | }).returningLastInsertedId() 34 | .executeInsert(); 35 | ``` 36 | 37 | The executed query is: 38 | 39 | ```sql 40 | insert into custom_company (name, is_big) 41 | values ($1, case when $2 then 'Y' else 'N' end) 42 | returning id 43 | ``` 44 | 45 | The parameters are: `[ 'My Big Company', true ]` 46 | 47 | The result type is: 48 | 49 | ```tsx 50 | const insertCustomCompany: Promise 51 | ``` 52 | 53 | Or a select: 54 | 55 | ```ts 56 | const selectAllBigCompanies = connection.selectFrom(tCustomCompany) 57 | .where(tCustomCompany.isBig) 58 | .select({ 59 | id: tCustomCompany.id, 60 | name: tCustomCompany.name, 61 | isBig: tCustomCompany.isBig 62 | }).executeSelectMany(); 63 | ``` 64 | 65 | The executed query is: 66 | 67 | ```sql 68 | select id as id, name as name, (is_big = 'Y') as isBig 69 | from custom_company 70 | where (is_big = 'Y') 71 | ``` 72 | 73 | The parameters are: `[]` 74 | 75 | The result type is: 76 | 77 | ```tsx 78 | const selectAllBigCompanies: Promise<{ 79 | id: number; 80 | name: string; 81 | isBig: boolean; 82 | }[]> 83 | ``` 84 | -------------------------------------------------------------------------------- /docs/advanced/synchronous-query-runners.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.7 4 | --- 5 | # Synchronous query runners 6 | 7 | Some query runners support executing queries synchronously if you provide a Promise implementation that supports it, like [synchronous-promise](https://www.npmjs.com/package/synchronous-promise). 8 | 9 | !!! success "Supported query runners that connect to a database" 10 | 11 | - [BetterSqlite3QueryRunner](../configuration/query-runners/recommended/better-sqlite3.md) 12 | - [Sqlite3WasmOO1QueryRunner](../configuration/query-runners/recommended/sqlite-wasm-OO1.md) 13 | 14 | !!! success "Supported general purposes query runners" 15 | 16 | - [InterceptorQueryRunner](../configuration/query-runners/general-purpose/InterceptorQueryRunner.md) 17 | - [LoggingQueryRunner](../configuration/query-runners/general-purpose/LoggingQueryRunner.md) 18 | - [MockQueryRunner](../configuration/query-runners/general-purpose/MockQueryRunner.md) 19 | - Others 20 | - [ConsoleLogQueryRunner](../configuration/query-runners/general-purpose/ConsoleLogQueryRunner.md) 21 | - [ConsoleLogNoopQueryRunner](../configuration/query-runners/general-purpose/ConsoleLogNoopQueryRunner.md) 22 | - [NoopQueryRunner](../configuration/query-runners/general-purpose/NoopQueryRunner.md) 23 | 24 | ## Usage Example 25 | 26 | ```ts 27 | import { BetterSqlite3QueryRunner } from "ts-sql-query/queryRunners/BetterSqlite3QueryRunner"; 28 | import * as betterSqlite3 from "better-sqlite3"; 29 | import { SynchronousPromise } from "synchronous-promise"; 30 | 31 | const db = betterSqlite3('foobar.db', options); 32 | 33 | async function main() { 34 | const connection = new DBConnection(new BetterSqlite3QueryRunner(db, { promise: SynchronousPromise })); 35 | // Do your queries here, surrounding it by the sync function. For example: 36 | const selectCompanies = sync( 37 | connection.selectFrom(tCompany) 38 | .where(tCompany.isBig) 39 | .select({ 40 | id: tCompany.id, 41 | name: tCompany.name 42 | }) 43 | .executeSelectMany() 44 | ); 45 | 46 | var result = sync(connection.insertInto...) 47 | result = sync(connection.update...) 48 | result = sync(connection.delete...) 49 | } 50 | ``` 51 | 52 | ## Unwrapping synchronous promises 53 | 54 | This utility function unwraps the result of a synchronous promise in a blocking manner, similar to how `await` unwraps regular promises. When using a promise implementation like [`synchronous-promise`](https://www.npmjs.com/package/synchronous-promise), which resolves synchronously and does not defer `.then` execution, this function allows interacting with `ts-sql-query` in a fully synchronous style. 55 | 56 | It is essential to ensure that the promise passed to `sync()` is truly synchronous — typically a database operation wrapped with `SynchronousPromise`. If the function detects that the operation was asynchronous (i.e. resolution was deferred), it throws an error, preventing misuse. 57 | 58 | ```ts 59 | /** 60 | * This function unwraps the synchronous promise in a synchronous way, 61 | * returning the result. 62 | */ 63 | function sync(promise: Promise): T { 64 | const UNSET = Symbol('unset'); 65 | 66 | let result: T | typeof UNSET = UNSET; 67 | let error: unknown | typeof UNSET = UNSET; 68 | 69 | promise.then( 70 | (r) => (result = r), 71 | (e) => (error = e), 72 | ); 73 | 74 | // Propagate error, if available 75 | if (error !== UNSET) { 76 | throw error; 77 | } 78 | 79 | // Propagate result, if available 80 | if (result !== UNSET) { 81 | return result; 82 | } 83 | 84 | // Note: This wrapper is to be used in combination with the `SynchronousPromise` type, 85 | // which is not strictly Promise-spec-compliant because it does not defer when calling 86 | // `.then`. See https://www.npmjs.com/package/synchronous-promise for more details. 87 | // To ensure that we're indeed using a synchronous promise, ensure that the promise resolved 88 | // immediately. 89 | throw new Error( 90 | 'You performed a real async operation (not a synchronous database call) ' + 91 | 'inside a function meant to execute synchronous database queries.', 92 | ); 93 | } 94 | ``` 95 | -------------------------------------------------------------------------------- /docs/advanced/tables-views-as-parameter.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.7 4 | --- 5 | # Passing tables and views as parameter 6 | 7 | In `ts-sql-query`, it's possible to pass a table or view as a parameter to a function. To do this properly, you must specify its type. This is where the utility types `TableOrViewOf` and `TableOrViewLeftJoinOf` come into play: 8 | 9 | - `TableOrViewOf`: for use with regular tables or views that allows creating a reference to the table or view. 10 | - `TableOrViewLeftJoinOf`: for the case, the table or view is marked for use in a left join. 11 | 12 | These types accept the referenced table or view as the first generic argument, and optionally an alias as the second. They are used as the base type for parameters that refer to a specific table or view instance. 13 | 14 | To access the columns of the table or view, you need to convert the reference into an actual instance using the `fromRef` function. The first argument is the table or view (or its class), and the second is the reference object. 15 | 16 | ```ts 17 | import { fromRef, TableOrViewLeftJoinOf, TableOrViewOf } from 'ts-sql-query/extras/types'; 18 | 19 | function buildNumberOfCustomersSubquery>(connection: DBConnection, companyRef: COMPANY) { 20 | const company = fromRef(tCompany, companyRef); 21 | 22 | return connection 23 | .subSelectUsing(company) 24 | .from(tCustomer) 25 | .where(tCustomer.companyId.equals(company.id)) 26 | .selectOneColumn(connection.countAll()) 27 | .forUseAsInlineQueryValue() 28 | .valueWhenNull(0); 29 | } 30 | 31 | async function getCompanyInfoWithNumberOfCustomers(connection: DBConnection, id: number) { 32 | const company = tCompany.as('company'); 33 | 34 | return await connection.selectFrom(company) 35 | .select({ 36 | id: company.id, 37 | name: company.name, 38 | numberOfCustomers: buildNumberOfCustomersSubquery(connection, company) 39 | }) 40 | .where(company.id.equals(id)) 41 | .executeSelectOne() 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/api/delete.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.3 4 | --- 5 | # Delete API 6 | 7 | This API provides methods to construct and execute SQL `DELETE` statements using a fluent interface in `ts-sql-query`. It allows for conditional deletion, joining with other tables, and optionally returning deleted data. 8 | 9 | ```ts 10 | interface DeleteExpression { 11 | /** Allows to create the where dynamically */ 12 | dynamicWhere(): this 13 | /** Allows to specify the where */ 14 | where(condition: BooleanValueSource): this 15 | 16 | /** Allows extending the WHERE or ON clause of a join using AND */ 17 | and(condition: BooleanValueSource): this 18 | /** Allows extending the WHERE or ON clause of a join using OR */ 19 | or(condition: BooleanValueSource): this 20 | 21 | /** 22 | * Execute the delete returning the number of deleted rows 23 | * 24 | * @param min Minimum number of rows that must be deleted. An exception is thrown if not reached 25 | * @param max Maximum number of rows that can be deleted. An exception is thrown if exceeded 26 | */ 27 | executeDelete(min?: number, max?: number): Promise 28 | /** Returns the sql query to be executed in the database */ 29 | query(): string 30 | /** Returns the required parameters by the sql query */ 31 | params(): any[] 32 | 33 | // Returning methods 34 | /** 35 | * Allows to specify the returning clause. 36 | * The object keys define the names of the resulting properties, and the values specify the ValueSource to retrieve from. 37 | */ 38 | returning(columns: DeleteReturningValues): this 39 | /** Returns the optional values as null instead of optional undefined values, can only used immediately after returning(...) */ 40 | projectingOptionalValuesAsNullable(): this 41 | /** 42 | * Allows to specify the returning clause of a query that returns only one column. 43 | * It receives as argument the ValueSource where the value will be obtained. 44 | */ 45 | returningOneColumn(column: AnyValueSource): this 46 | /** Executes the delete query and returns one or no result */ 47 | executeDeleteNoneOrOne(): Promise 48 | /** 49 | * Execute the delete query that returns one result from the database. 50 | * If no result is returned by the database an exception will be thrown. 51 | */ 52 | executeDeleteOne(min?: number, max?: number): Promise 53 | /** 54 | * Execute the delete query that returns zero or many results from the database 55 | * 56 | * @param min Indicate the minimum of rows that must be deleted, 57 | * if the minimum is not reached an exception will be thrown 58 | * @param max Indicate the maximum of rows that must be deleted, 59 | * if the maximum is exceeded an exception will be thrown 60 | */ 61 | executeDeleteMany(): Promise 62 | 63 | /** Allows to add a using (like a from that doesn't delete) to the delete query */ 64 | using(table: Table | View): this 65 | 66 | /** Allows to add a join to the delete query */ 67 | join(table: Table | View): this 68 | /** Allows to add a inner join to the delete query */ 69 | innerJoin(table: Table | View): this 70 | /** 71 | * Allows to add a left join to the delete query. 72 | * Note: You must call forUseInLeftJoin on a table or view before using it in a left join 73 | */ 74 | leftJoin(source: OuterJoinSource): this 75 | /** 76 | * Allows to add a left outer join to the delete query. 77 | * Note: You must call forUseInLeftJoin on a table or view before using it in a left join 78 | */ 79 | leftOuterJoin(source: OuterJoinSource): this 80 | 81 | /** Allows to create the on clause of a join dynamically */ 82 | dynamicOn(): this 83 | /** Allows to specify the on clause of a join */ 84 | on(condition: BooleanValueSource): this 85 | 86 | customizeQuery(customization: { 87 | afterDeleteKeyword?: RawFragment 88 | afterQuery?: RawFragment 89 | queryExecutionName?: string 90 | queryExecutionMetadata?: any 91 | }): this 92 | } 93 | ``` 94 | 95 | ```ts 96 | /** 97 | * Returning projection of the values that will be retrieved from the database. 98 | * 99 | * It must be an object where the name of the property is the name of the resulting property 100 | * and the value is the ValueSource where the value will be obtained. 101 | */ 102 | type DeleteReturningValues = { [columnName: string]: AnyValueSource } 103 | ``` 104 | -------------------------------------------------------------------------------- /docs/api/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.3 4 | --- 5 | # API Introduction 6 | 7 | The most common operations on data are supported by `ts-sql-query`. When a database does not support a specific feature, `ts-sql-query` attempts to emulate it within the generated SQL. If that is not possible, a compile-time error will be raised in your source code. 8 | 9 | Some parts of the API follow a fluent interface design, meaning that each method returns an object exposing the next valid operations for that stage of the query. 10 | 11 | Below is a simplified overview of the `ts-sql-query` APIs. 12 | -------------------------------------------------------------------------------- /docs/api/type-adpaters.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.3 4 | --- 5 | # Type adapters API 6 | 7 | This page documents the API for defining and customizing type adapters in `ts-sql-query`. Type adapters transform values when reading from or writing to the database, supporting use cases such as custom serialization, non-standard boolean mappings, and enforced type casting. 8 | 9 | Type adapters control how values are serialized when writing to the database and deserialized when reading from it. They can be applied at the field level (when defining a table or view), or globally by overriding the `transformValueFromDB` and `transformValueToDB` methods. 10 | 11 | !!! info 12 | 13 | Type adapter definitions are in the file `ts-sql-query/TypeAdapter`. 14 | 15 | !!! tip 16 | 17 | The `CustomBooleanTypeAdapter` lets you define custom values to represent booleans when they don’t match the database's default boolean format. For example, a column might store `'yes'` for true and `'no'` for false instead of using standard boolean types. See [Custom booleans values](../advanced/custom-booleans-values.md) for more information. 18 | 19 | ```ts 20 | interface TypeAdapter { 21 | transformValueFromDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown 22 | transformValueToDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown 23 | transformPlaceholder?(placeholder: string, type: string, forceTypeCast: boolean, valueSentToDB: unknown, next: DefaultTypeAdapter): string 24 | } 25 | ``` 26 | 27 | ```ts 28 | interface DefaultTypeAdapter { 29 | transformValueFromDB(value: unknown, type: string): unknown 30 | transformValueToDB(value: unknown, type: string): unknown 31 | transformPlaceholder(placeholder: string, type: string, forceTypeCast: boolean, valueSentToDB: unknown): string 32 | } 33 | ``` 34 | 35 | ```ts 36 | class CustomBooleanTypeAdapter implements TypeAdapter { 37 | readonly trueValue: number | string 38 | readonly falseValue: number | string 39 | 40 | constructor(trueValue: number, falseValue: number) 41 | constructor(trueValue: string, falseValue: string) 42 | 43 | transformValueFromDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown 44 | transformValueToDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown 45 | } 46 | ``` 47 | 48 | ```ts 49 | class ForceTypeCast implements TypeAdapter { 50 | transformValueFromDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown 51 | transformValueToDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown 52 | transformPlaceholder(placeholder: string, type: string, _forceTypeCast: boolean, valueSentToDB: unknown, next: DefaultTypeAdapter): string 53 | } 54 | ``` 55 | 56 | !!! tip 57 | 58 | You can create custom type adapters by implementing the `TypeAdapter` interface, giving you full control over how your application communicates with the database. 59 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/additional/mysql.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.573 4 | --- 5 | # mysql 6 | 7 | This page explains how to use `ts-sql-query` with the [`mysql`](https://www.npmjs.com/package/mysql) driver. It covers two approaches: using a connection pool or using a single connection directly. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [MariaDB](../../supported-databases/mariadb.md) 12 | - [MySQL](../../supported-databases/mysql.md) 13 | 14 | !!! warning "Do not share connections between requests" 15 | 16 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 17 | 18 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 19 | 20 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 21 | 22 | ## Using a connection pool 23 | 24 | Enables executing queries through a [mysql](https://www.npmjs.com/package/mysql) connection obtained from a pool. 25 | 26 | ```ts 27 | import { createPool } from "mysql"; 28 | import { MySqlPoolQueryRunner } from "ts-sql-query/queryRunners/MySqlPoolQueryRunner"; 29 | 30 | const pool = createPool({ 31 | connectionLimit : 10, 32 | host : 'example.org', 33 | user : 'bob', 34 | password : 'secret', 35 | database : 'my_db' 36 | }); 37 | 38 | async function main() { 39 | const connection = new DBConnection(new MySqlPoolQueryRunner(pool)); 40 | // Do your queries here 41 | } 42 | ``` 43 | 44 | ## Using a single connection 45 | 46 | Enables executing queries through a dedicated [mysql](https://www.npmjs.com/package/mysql) connection. 47 | 48 | ```ts 49 | import { createPool } from "mysql"; 50 | import { MySqlQueryRunner } from "ts-sql-query/queryRunners/MySqlQueryRunner"; 51 | 52 | const pool = createPool({ 53 | connectionLimit : 10, 54 | host : 'example.org', 55 | user : 'bob', 56 | password : 'secret', 57 | database : 'my_db' 58 | }); 59 | 60 | function main() { 61 | pool.getConnection((error, mysqlConnection) => { 62 | if (error) { 63 | throw error; 64 | } 65 | try { 66 | const connection = new DBConnection(new MySqlQueryRunner(mysqlConnection)); 67 | doYourLogic(connection).finally(() => { 68 | mysqlConnection.release(); 69 | }); 70 | } catch(e) { 71 | mysqlConnection.release(); 72 | throw e; 73 | } 74 | }); 75 | } 76 | 77 | async function doYourLogic(connection: DBConnection) { 78 | // Do your queries here 79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/additional/sqlite.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.573 4 | --- 5 | # sqlite 6 | 7 | This runner provides integration with the [sqlite](https://www.npmjs.com/package/sqlite) driver, allowing `ts-sql-query` to execute queries on SQLite databases. It wraps an instance of a connected SQLite database and must be used in combination with a `ts-sql-query` connection. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [SQLite](../../supported-databases/sqlite.md) 12 | 13 | !!! warning "Do not share connections between requests" 14 | 15 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 16 | 17 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 18 | 19 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 20 | 21 | ## Using a single connection 22 | 23 | Enables executing queries through a dedicated [sqlite](https://www.npmjs.com/package/sqlite) connection. 24 | 25 | ```ts 26 | import { Database } from 'sqlite3'; 27 | import { open } from 'sqlite'; 28 | import { SqliteQueryRunner } from "ts-sql-query/queryRunners/SqliteQueryRunner"; 29 | 30 | const dbPromise = open({ 31 | filename: './database.sqlite', 32 | driver: sqlite3.Database 33 | }); 34 | 35 | async function main() { 36 | const db = await dbPromise; 37 | const connection = new DBConnection(new SqliteQueryRunner(db)); 38 | // Do your queries here 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/general-purpose/ConsoleLogNoopQueryRunner.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.01 4 | --- 5 | # ConsoleLogNoopQueryRunner 6 | 7 | A query runner that simulates a database connection by logging all queries to the console using `console.log`, and always returns empty results. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [MariaDB](../../supported-databases/mariadb.md) 12 | - [MySQL](../../supported-databases/mysql.md) 13 | - [Oracle](../../supported-databases/oracle.md) 14 | - [PostgreSQL](../../supported-databases/postgresql.md) 15 | - [SQLite](../../supported-databases/sqlite.md) 16 | - [SQL Server](../../supported-databases/sqlserver.md) 17 | 18 | !!! tip 19 | 20 | `ConsoleLogNoopQueryRunner` supports synchronous query execution. See the [Synchronous query runners](../../../advanced/synchronous-query-runners.md) for more information. 21 | 22 | !!! warning "Do not share connections between requests" 23 | 24 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 25 | 26 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 27 | 28 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 29 | 30 | ## Usage Example 31 | 32 | ```ts 33 | import { ConsoleLogNoopQueryRunner } from "ts-sql-query/queryRunners/ConsoleLogNoopQueryRunner"; 34 | 35 | async function main() { 36 | const connection = new DBConnection(new ConsoleLogNoopQueryRunner()); 37 | // Do your queries here 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/general-purpose/ConsoleLogQueryRunner.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.01 4 | --- 5 | # ConsoleLogQueryRunner 6 | 7 | A general-purpose query runner that logs all executed SQL statements to the standard output using `console.log`. It wraps another query runner, delegating execution while recording detailed log information for inspection or debugging. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [MariaDB](../../supported-databases/mariadb.md) 12 | - [MySQL](../../supported-databases/mysql.md) 13 | - [Oracle](../../supported-databases/oracle.md) 14 | - [PostgreSQL](../../supported-databases/postgresql.md) 15 | - [SQLite](../../supported-databases/sqlite.md) 16 | - [SQL Server](../../supported-databases/sqlserver.md) 17 | 18 | !!! tip 19 | 20 | `ConsoleLogQueryRunner` supports synchronous query execution. See the [Synchronous query runners](../../../advanced/synchronous-query-runners.md) for more information. 21 | 22 | !!! warning "Do not share connections between requests" 23 | 24 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 25 | 26 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 27 | 28 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 29 | 30 | ## Usage Example 31 | 32 | ```ts 33 | import { ConsoleLogQueryRunner } from "ts-sql-query/queryRunners/ConsoleLogQueryRunner"; 34 | 35 | async function main() { 36 | const connection = new DBConnection(new ConsoleLogQueryRunner(otherQueryRunner)); 37 | // Do your queries here 38 | } 39 | ``` 40 | 41 | ## API Overview 42 | 43 | The constructor receives a secondary optional argument with the following definition: 44 | 45 | ```ts 46 | interface ConsoleLogQueryRunnerOpts { 47 | timeGranularity?: 'ms' | 'us' | 'ns' // Granularity of time and duration logged, default 'ms' 48 | logTimestamps?: boolean // Include the time value when the log happened in nanoseconds since an arbitrary starting point, default false 49 | logDurations?: boolean // Include the duration of the query execution, default false 50 | logResults?: boolean // Include the result object in the log, default false 51 | paramsAsObject?: boolean // Write in the log the query, params, result and error wrapped in an object, default false 52 | includeLogPhase?: boolean // Write the phase name ('onQuery', 'onQueryResult', 'onQueryError') in the log, default false 53 | } 54 | ``` 55 | 56 | !!! info 57 | 58 | In case the provided query runner doesn't support low-level transaction management, synthetic `beginTransaction`, `commit`, and `rollback` statements will be emitted to allow you to see them in the log. 59 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/general-purpose/LoggingQueryRunner.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.575 4 | --- 5 | # LoggingQueryRunner 6 | 7 | A general-purpose query runner that intercepts all queries and delegates their execution to another query runner while allowing you to log query details. Useful for debugging, performance monitoring, and auditing executed statements. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [MariaDB](../../supported-databases/mariadb.md) 12 | - [MySQL](../../supported-databases/mysql.md) 13 | - [Oracle](../../supported-databases/oracle.md) 14 | - [PostgreSQL](../../supported-databases/postgresql.md) 15 | - [SQLite](../../supported-databases/sqlite.md) 16 | - [SQL Server](../../supported-databases/sqlserver.md) 17 | 18 | !!! tip 19 | 20 | `LoggingQueryRunner` supports synchronous query execution. See the [Synchronous query runners](../../../advanced/synchronous-query-runners.md) for more information. 21 | 22 | !!! warning "Do not share connections between requests" 23 | 24 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 25 | 26 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 27 | 28 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 29 | 30 | ## Usage Example 31 | 32 | ```ts 33 | import { LoggingQueryRunner } from "ts-sql-query/queryRunners/LoggingQueryRunner"; 34 | 35 | async function main() { 36 | const connection = new DBConnection(new LoggingQueryRunner({ 37 | onQuery(queryType, query, params) { 38 | console.log('onQuery', queryType, query, params, { startedAt }) 39 | }, 40 | onQueryResult(queryType, query, params, result) { 41 | console.log('onQueryResult', queryType, query, params, result, { startedAt, endedAt }) 42 | }, 43 | onQueryError(queryType, query, params, error) { 44 | console.log('onQueryError', queryType, query, params, error, { startedAt, endedAt }) 45 | } 46 | }, otherQueryRunner)); 47 | // Do your queries here 48 | } 49 | ``` 50 | 51 | ## API Overview 52 | 53 | The `LoggingQueryRunner` receives an object as first argument of the constructor that can define the following functions: 54 | 55 | - **`onQuery`**: Executed before the query. 56 | - **`onQueryResult`**: Executed after the successful execution of the query. 57 | - **`onQueryError`**: Executed after the query in case of error. 58 | 59 | All these functions receive as argument: 60 | 61 | - **`type: QueryType`**: type of the query to be executed. The `QueryType` is defined as: 62 | 63 | ```ts 64 | type QueryType = 'selectOneRow' | 'selectManyRows' | 'selectOneColumnOneRow' | 'selectOneColumnManyRows' | 65 | 'insert' | 'insertReturningLastInsertedId' | 'insertReturningMultipleLastInsertedId' | 66 | 'insertReturningOneRow' | 'insertReturningManyRows' | 'insertReturningOneColumnOneRow' | 'insertReturningOneColumnManyRows' | 67 | 'update' | 'updateReturningOneRow' | 'updateReturningManyRows' | 'updateReturningOneColumnOneRow' | 'updateReturningOneColumnManyRows' | 68 | 'delete' | 'deleteReturningOneRow' | 'deleteReturningManyRows' | 'deleteReturningOneColumnOneRow' | 'deleteReturningOneColumnManyRows' | 69 | 'executeProcedure' | 'executeFunction' | 'beginTransaction' | 'commit' | 'rollback' | 70 | 'executeDatabaseSchemaModification' | 'executeConnectionConfiguration' 71 | ``` 72 | 73 | - **`query: string`**: query required to be executed, empty in the case of `beginTransaction`, `commit` or `rollback` 74 | - **`params: any[]`**: parameters received by the query. 75 | - **`result: any`**: (only in `onQueryResult`) result of the execution of the query. 76 | - **`error: any`**: (only in `onQueryError`) error that happens executing the query. 77 | - **`startedAt`**: elapsed time value in nanoseconds before the query execution. 78 | - **`endedAt`**: (only in `onQueryResult` or `onQueryError`) elapsed time value in nanoseconds after the query execution. 79 | 80 | !!! info 81 | 82 | - `onQuery`, `onQueryResult`, and `onQueryError` are optional; you can define only the methods that you need. 83 | - In case the provided query runner doesn't support low-level transaction management, fake `beginTransaction`, `commit`, and `rollback` operations will be emitted to allow you to see them in the log. 84 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/general-purpose/NoopQueryRunner.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.01 4 | --- 5 | # NoopQueryRunner 6 | 7 | A query runner that simulates a database connection and always returns empty results. Useful for testing or disabling execution without modifying application logic. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [MariaDB](../../supported-databases/mariadb.md) 12 | - [MySQL](../../supported-databases/mysql.md) 13 | - [Oracle](../../supported-databases/oracle.md) 14 | - [PostgreSQL](../../supported-databases/postgresql.md) 15 | - [SQLite](../../supported-databases/sqlite.md) 16 | - [SQL Server](../../supported-databases/sqlserver.md) 17 | 18 | !!! tip 19 | 20 | `NoopQueryRunner` supports synchronous query execution. See the [Synchronous query runners](../../../advanced/synchronous-query-runners.md) for more information. 21 | 22 | !!! warning "Do not share connections between requests" 23 | 24 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 25 | 26 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 27 | 28 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 29 | 30 | ## Usage Example 31 | 32 | ```ts 33 | import { NoopQueryRunner } from "ts-sql-query/queryRunners/NoopQueryRunner"; 34 | 35 | async function main() { 36 | const connection = new DBConnection(new NoopQueryRunner()); 37 | // Queries here will not hit a real database 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/recommended/better-sqlite3.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.577 4 | --- 5 | # better-sqlite3 6 | 7 | This runner provides integration with the [better-sqlite3](https://www.npmjs.com/package/better-sqlite3) driver, allowing `ts-sql-query` to execute queries on SQLite databases. It wraps an instance of a connected SQLite database and must be used in combination with a `ts-sql-query` connection. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [SQLite](../../supported-databases/sqlite.md) 12 | 13 | !!! tip 14 | 15 | better-sqlite3 supports synchronous query execution. See the [Synchronous query runners](../../../advanced/synchronous-query-runners.md) for more information. 16 | 17 | !!! warning "Do not share connections between requests" 18 | 19 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 20 | 21 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 22 | 23 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 24 | 25 | ## Using a single connection 26 | 27 | Enables executing queries through a dedicated [better-sqlite3](https://www.npmjs.com/package/better-sqlite3) connection. 28 | 29 | ```ts 30 | import { BetterSqlite3QueryRunner } from "ts-sql-query/queryRunners/BetterSqlite3QueryRunner"; 31 | import * as betterSqlite3 from "better-sqlite3"; 32 | 33 | const db = betterSqlite3('foobar.db', options); 34 | 35 | async function main() { 36 | const connection = new DBConnection(new BetterSqlite3QueryRunner(db)); 37 | // Do your queries here 38 | } 39 | ``` 40 | 41 | ## better-sqlite3 and UUIDs 42 | 43 | To work with [UUIDs in SQLite](../../supported-databases/sqlite.md#uuid-strategies) the default strategy is `uuid-extension` that requires the [uuid extension](https://sqlite.org/src/file?name=ext/misc/uuid.c); you can provide a compatible implementation as indicated here: 44 | 45 | ```ts 46 | import * as betterSqlite3 from "better-sqlite3"; 47 | import { fromBinaryUUID, toBinaryUUID } from "binary-uuid"; 48 | import { v4 as uuidv4 } from "uuid"; 49 | 50 | const db = betterSqlite3(/* ... */); 51 | 52 | // Implement uuid extension functions 53 | 54 | db.function('uuid', uuidv4) 55 | db.function('uuid_str', fromBinaryUUID) 56 | db.function('uuid_blob', toBinaryUUID) 57 | 58 | // ... 59 | ``` 60 | 61 | !!! warning 62 | 63 | The binary representation used in this implementation is not intended to be compatible with SQLite’s optional UUID extension. 64 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/recommended/mariadb.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.577 4 | --- 5 | # mariadb 6 | 7 | This page explains how to use `ts-sql-query` with the [mariadb](https://www.npmjs.com/package/mariadb) driver. It covers two approaches: using a connection pool or using a single connection directly. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [MariaDB](../../supported-databases/mariadb.md) 12 | - [MySQL](../../supported-databases/mysql.md) 13 | 14 | !!! warning "Do not share connections between requests" 15 | 16 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 17 | 18 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 19 | 20 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 21 | 22 | ## Using a connection pool 23 | 24 | Enables executing queries through a [mariadb](https://www.npmjs.com/package/mariadb) connection obtained from a pool. 25 | 26 | ```ts 27 | import { createPool } from "mariadb"; 28 | import { MariaDBPoolQueryRunner } from "ts-sql-query/queryRunners/MariaDBPoolQueryRunner"; 29 | 30 | const pool = createPool({ 31 | host: 'mydb.com', 32 | user: 'myUser', 33 | password: 'myPwd', 34 | database: 'myDB', 35 | connectionLimit: 5 36 | }); 37 | 38 | async function main() { 39 | const connection = new DBConnection(new MariaDBPoolQueryRunner(pool)); 40 | // Do your queries here 41 | } 42 | ``` 43 | 44 | ## Using a single connection 45 | 46 | Enables executing queries through a dedicated [mariadb](https://www.npmjs.com/package/mariadb) connection. 47 | 48 | ```ts 49 | import { createPool } from "mariadb"; 50 | import { MariaDBQueryRunner } from "ts-sql-query/queryRunners/MariaDBQueryRunner"; 51 | 52 | const pool = createPool({ 53 | host: 'mydb.com', 54 | user: 'myUser', 55 | password: 'myPwd', 56 | database: 'myDB', 57 | connectionLimit: 5 58 | }); 59 | 60 | async function main() { 61 | const mariaDBConnection = await pool.getConnection(); 62 | try { 63 | const connection = new DBConnection(new MariaDBQueryRunner(mariaDBConnection)); 64 | // Do your queries here 65 | } finally { 66 | mariaDBConnection.release(); 67 | } 68 | } 69 | ``` 70 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/recommended/mssql.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.577 4 | --- 5 | # mssql 6 | 7 | This page explains how to use `ts-sql-query` with the [mssql](https://www.npmjs.com/package/mssql) driver. It covers two approaches: using a connection pool promise or using a connection pool. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [SQL Server](../../supported-databases/sqlserver.md) 12 | 13 | !!! warning "Do not share connections between requests" 14 | 15 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 16 | 17 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 18 | 19 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 20 | 21 | ## Using a connection pool promise 22 | 23 | Enables executing queries through a [mssql](https://www.npmjs.com/package/mssql) connection obtained from a pool promise. 24 | 25 | ```ts 26 | import { ConnectionPool } from 'mssql' 27 | import { MssqlPoolPromiseQueryRunner } from "./queryRunners/MssqlPoolPromiseQueryRunner"; 28 | 29 | const poolPromise = new ConnectionPool({ 30 | user: '...', 31 | password: '...', 32 | server: 'localhost', 33 | database: '...' 34 | }).connect(); 35 | 36 | async function main() { 37 | const connection = new DBConnection(new MssqlPoolPromiseQueryRunner(poolPromise)); 38 | // Do your queries here 39 | } 40 | ``` 41 | 42 | ## Using a connection pool 43 | 44 | Enables executing queries through a [mssql](https://www.npmjs.com/package/mssql) connection obtained from a pool. 45 | 46 | ```ts 47 | import { ConnectionPool } from 'mssql' 48 | import { MssqlPoolQueryRunner } from "./queryRunners/MssqlPoolQueryRunner"; 49 | 50 | const poolPromise = new ConnectionPool({ 51 | user: '...', 52 | password: '...', 53 | server: 'localhost', 54 | database: '...' 55 | }).connect(); 56 | 57 | async function main() { 58 | const mssqlPool = await poolPromise; 59 | const connection = new DBConnection(new MssqlPoolQueryRunner(mssqlPool)); 60 | // Do your queries here 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/recommended/mysql2.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.577 4 | --- 5 | # mysql2 6 | 7 | This page explains how to use `ts-sql-query` with the [mysql2](https://www.npmjs.com/package/mysql2) driver. It covers two approaches: using a connection pool or using a single connection directly. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [MariaDB](../../supported-databases/mariadb.md) 12 | - [MySQL](../../supported-databases/mysql.md) 13 | 14 | !!! warning "Do not share connections between requests" 15 | 16 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 17 | 18 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 19 | 20 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 21 | 22 | ## Using a connection pool 23 | 24 | Executes queries through a [mysql2](https://www.npmjs.com/package/mysql2) connection obtained from a pool. 25 | 26 | ```ts 27 | import { createPool } from "mysql2"; 28 | import { MySql2PoolQueryRunner } from "ts-sql-query/queryRunners/MySql2PoolQueryRunner"; 29 | 30 | const pool = createPool({ 31 | host: 'localhost', 32 | user: 'user', 33 | password: 'secret', 34 | database: 'test', 35 | waitForConnections: true, 36 | connectionLimit: 10, 37 | queueLimit: 0 38 | }); 39 | 40 | async function main() { 41 | const connection = new DBConnection(new MySql2PoolQueryRunner(pool)); 42 | // Do your queries here 43 | } 44 | ``` 45 | 46 | ## Using a single connection 47 | 48 | Executes queries through a dedicated [mysql2](https://www.npmjs.com/package/mysql2) connection. 49 | 50 | ```ts 51 | import { createPool } from "mysql2"; 52 | import { MySql2QueryRunner } from "ts-sql-query/queryRunners/MySql2QueryRunner"; 53 | 54 | const pool = createPool({ 55 | host: 'localhost', 56 | user: 'user', 57 | password: 'secret', 58 | database: 'test', 59 | waitForConnections: true, 60 | connectionLimit: 10, 61 | queueLimit: 0 62 | }); 63 | 64 | function main() { 65 | pool.getConnection((error, mysql2Connection) => { 66 | if (error) { 67 | throw error; 68 | } 69 | try { 70 | const connection = new DBConnection(new MySql2QueryRunner(mysql2Connection)); 71 | doYourLogic(connection).finally(() => { 72 | mysql2Connection.release(); 73 | }); 74 | } catch(e) { 75 | mysql2Connection.release(); 76 | throw e; 77 | } 78 | }); 79 | } 80 | 81 | async function doYourLogic(connection: DBConnection) { 82 | // Do your queries here 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/recommended/oracledb.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.577 4 | --- 5 | # oracledb 6 | 7 | This page explains how to use `ts-sql-query` with the [oracledb](https://www.npmjs.com/package/oracledb) driver. It covers three approaches: using a connection pool promise, using a connection pool or using a single connection directly. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [Oracle](../../supported-databases/oracle.md) 12 | 13 | !!! warning "Do not share connections between requests" 14 | 15 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 16 | 17 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 18 | 19 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 20 | 21 | ## Using a connection pool promise 22 | 23 | Executes queries through a [oracledb](https://www.npmjs.com/package/oracledb) connection obtained from a pool promise. 24 | 25 | ```ts 26 | import { createPool } from 'oracledb'; 27 | import { OracleDBPoolPromiseQueryRunner } from "ts-sql-query/queryRunners/OracleDBPoolPromiseQueryRunner"; 28 | 29 | const poolPromise = createPool({ 30 | user: 'user', 31 | password: 'pwd', 32 | connectString: 'localhost/XEPDB1' 33 | }); 34 | 35 | async function closePoolAndExit() { 36 | try { 37 | const pool = await poolPromise; 38 | await pool.close(10); 39 | process.exit(0); 40 | } catch(err) { 41 | process.exit(1); 42 | } 43 | } 44 | 45 | process 46 | .once('SIGTERM', closePoolAndExit) 47 | .once('SIGINT', closePoolAndExit) 48 | .once('beforeExit', closePoolAndExit); 49 | 50 | async function main() { 51 | const connection = new DBConnection(new OracleDBPoolPromiseQueryRunner(poolPromise)); 52 | // Do your queries here 53 | } 54 | ``` 55 | 56 | ## Using a connection pool 57 | 58 | Executes queries through a [oracledb](https://www.npmjs.com/package/oracledb) connection obtained from a pool. 59 | 60 | ```ts 61 | import { createPool } from 'oracledb'; 62 | import { OracleDBPoolQueryRunner } from "ts-sql-query/queryRunners/OracleDBPoolQueryRunner"; 63 | 64 | const poolPromise = createPool({ 65 | user: 'user', 66 | password: 'pwd', 67 | connectString: 'localhost/XEPDB1' 68 | }); 69 | 70 | async function closePoolAndExit() { 71 | try { 72 | const pool = await poolPromise; 73 | await pool.close(10); 74 | process.exit(0); 75 | } catch(err) { 76 | process.exit(1); 77 | } 78 | } 79 | 80 | process 81 | .once('SIGTERM', closePoolAndExit) 82 | .once('SIGINT', closePoolAndExit) 83 | .once('beforeExit', closePoolAndExit); 84 | 85 | async function main() { 86 | const pool = await poolPromise; 87 | const connection = new DBConnection(new OracleDBPoolQueryRunner(pool)); 88 | // Do your queries here 89 | } 90 | ``` 91 | 92 | ## Using a single connection 93 | 94 | Executes queries through a dedicated [oracledb](https://www.npmjs.com/package/oracledb) connection. 95 | 96 | ```ts 97 | import { createPool } from 'oracledb'; 98 | import { OracleDBQueryRunner } from "ts-sql-query/queryRunners/OracleDBQueryRunner"; 99 | 100 | async function init() { 101 | try { 102 | await createPool({ 103 | user: 'user', 104 | password: 'pwd', 105 | connectString: 'localhost/XEPDB1' 106 | }); 107 | await main(); 108 | } finally { 109 | await closePoolAndExit(); 110 | } 111 | } 112 | 113 | async function closePoolAndExit() { 114 | try { 115 | await oracledb.getPool().close(10); 116 | process.exit(0); 117 | } catch(err) { 118 | process.exit(1); 119 | } 120 | } 121 | 122 | process 123 | .once('SIGTERM', closePoolAndExit) 124 | .once('SIGINT', closePoolAndExit) 125 | .once('beforeExit', closePoolAndExit); 126 | 127 | init(); 128 | 129 | async function main() { 130 | const oracleConnection = await oracledb.getConnection(); 131 | try { 132 | const connection = new DBConnection(new OracleDBQueryRunner(oracleConnection)); 133 | // Do your queries here 134 | } finally { 135 | await oracleConnection.close(); 136 | } 137 | } 138 | ``` 139 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/recommended/pg.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.577 4 | --- 5 | # pg 6 | 7 | This page explains how to use `ts-sql-query` with the [pg](https://www.npmjs.com/package/pg) driver. It covers two approaches: using a connection pool or using a single connection directly. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [PostgreSQL](../../supported-databases/postgresql.md) 12 | 13 | !!! warning "Do not share connections between requests" 14 | 15 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 16 | 17 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 18 | 19 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 20 | 21 | ## Using a connection pool 22 | 23 | Executes queries through a [pg](https://www.npmjs.com/package/pg) connection obtained from a pool. 24 | 25 | ```ts 26 | import { Pool, PoolClient } from 'pg'; 27 | import { PgPoolQueryRunner } from "ts-sql-query/queryRunners/PgPoolQueryRunner"; 28 | 29 | const pool = new Pool({ 30 | user: 'dbuser', 31 | host: 'database.server.com', 32 | database: 'mydb', 33 | password: 'secretpassword', 34 | port: 3211, 35 | }); 36 | 37 | async function main() { 38 | const connection = new DBConnection(new PgPoolQueryRunner(pool)); 39 | // Do your queries here 40 | } 41 | ``` 42 | 43 | !!! warning 44 | 45 | If you want to allow to have nested transactions you must create the instance as `new PgPoolQueryRunner(pool, {allowNestedTransactions: true})` 46 | 47 | ## Using a single connection 48 | 49 | Executes queries through a dedicated [pg](https://www.npmjs.com/package/pg) connection. 50 | 51 | ```ts 52 | import { Pool, PoolClient } from 'pg'; 53 | import { PgQueryRunner } from "ts-sql-query/queryRunners/PgQueryRunner"; 54 | 55 | const pool = new Pool({ 56 | user: 'dbuser', 57 | host: 'database.server.com', 58 | database: 'mydb', 59 | password: 'secretpassword', 60 | port: 3211, 61 | }); 62 | 63 | async function main() { 64 | const pgConnection = await pool.connect(); 65 | try { 66 | const connection = new DBConnection(new PgQueryRunner(pgConnection)); 67 | // Do your queries here 68 | } finally { 69 | pgConnection.release(); 70 | } 71 | } 72 | ``` 73 | 74 | !!! warning 75 | 76 | If you want to allow to have nested transactions you must create the instance as `new PgQueryRunner(pgConnection, {allowNestedTransactions: true})` 77 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/recommended/postgres.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.577 4 | --- 5 | # postgres 6 | 7 | This page explains how to use `ts-sql-query` with the [postgres](https://github.com/porsager/postgres) (aka Postgres.js) driver. It covers two approaches: using a connection pool or using a single connection directly. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [PostgreSQL](../../supported-databases/postgresql.md) 12 | 13 | !!! warning "Do not share connections between requests" 14 | 15 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 16 | 17 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 18 | 19 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 20 | 21 | ## Using a connection pool 22 | 23 | Executes queries through a [postgres](https://github.com/porsager/postgres) (aka Postgres.js) connection obtained from a pool. 24 | 25 | ```ts 26 | import * as postgres from 'postgres'; 27 | import { PostgresQueryRunner } from '../queryRunners/PostgresQueryRunner'; 28 | 29 | const sql = postgres({ 30 | user: 'dbuser', 31 | host: 'database.server.com', 32 | database: 'mydb', 33 | password: 'secretpassword', 34 | port: 3211, 35 | }); 36 | 37 | async function main() { 38 | const connection = new DBConnection(new PostgresQueryRunner(sql)); 39 | // Do your queries here 40 | } 41 | ``` 42 | 43 | !!! warning "Limitation" 44 | 45 | Low-level transaction management functions (`connection.beginTransaction`, `connection.commit`, `connection.rollback`) are not supported; you must use `connection.transaction` instead. 46 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/recommended/sqlite-wasm-OO1.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.577 4 | --- 5 | # sqlite-wasm OO1 6 | 7 | This runner provides integration with the WebAssembly-based [@sqlite.org/sqlite-wasm](https://www.npmjs.com/package/@sqlite.org/sqlite-wasm) [Object Oriented API 1](https://sqlite.org/wasm/doc/trunk/api-oo1.md), allowing `ts-sql-query` to execute queries on SQLite databases. It wraps an instance of a connected SQLite database and must be used in combination with a `ts-sql-query` connection. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [SQLite](../../supported-databases/sqlite.md) 12 | 13 | !!! tip 14 | 15 | better-sqlite3 supports synchronous query execution. See the [Synchronous query runners](../../../advanced/synchronous-query-runners.md) for more information. 16 | 17 | !!! warning "Do not share connections between requests" 18 | 19 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 20 | 21 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 22 | 23 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 24 | 25 | ## Using a single connection 26 | 27 | Enables executing queries through a dedicated WebAssembly-based [@sqlite.org/sqlite-wasm](https://www.npmjs.com/package/@sqlite.org/sqlite-wasm) [Object Oriented API 1](https://sqlite.org/wasm/doc/trunk/api-oo1.md) connection. 28 | 29 | ```ts 30 | import sqlite3InitModule from '@sqlite.org/sqlite-wasm'; 31 | import { Sqlite3WasmOO1QueryRunner } from "ts-sql-query/queryRunners/Sqlite3WasmOO1QueryRunner"; 32 | 33 | async function main() { 34 | const sqlite3 = await sqlite3InitModule(); 35 | const db: Database = new sqlite3.oo1.DB(); 36 | const connection = new DBConnection(new Sqlite3WasmOO1QueryRunner(db)); 37 | // Do your queries here 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/configuration/query-runners/recommended/sqlite3.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.577 4 | --- 5 | # sqlite3 6 | 7 | This runner provides integration with the [sqlite3](https://www.npmjs.com/package/sqlite3) driver, allowing `ts-sql-query` to execute queries on SQLite databases. It wraps an instance of a connected SQLite database and must be used in combination with a `ts-sql-query` connection. 8 | 9 | !!! success "Supported databases" 10 | 11 | - [SQLite](../../supported-databases/sqlite.md) 12 | 13 | !!! warning "Do not share connections between requests" 14 | 15 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 16 | 17 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 18 | 19 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 20 | 21 | ## Using a single connection 22 | 23 | Enables executing queries through a dedicated [sqlite3](https://www.npmjs.com/package/sqlite3) connection. 24 | 25 | ```ts 26 | import { Database } from 'sqlite3'; 27 | import { Sqlite3QueryRunner } from "ts-sql-query/queryRunners/Sqlite3QueryRunner"; 28 | 29 | const db = new Database('./database.sqlite'); 30 | 31 | async function main() { 32 | const connection = new DBConnection(new Sqlite3QueryRunner(db)); 33 | // Do your queries here 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/configuration/supported-databases/mariadb.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.59 4 | --- 5 | # MariaDB 6 | 7 | This page describes how `ts-sql-query` integrates with **[MariaDB](https://mariadb.org)**, including dialect-specific behavior, configuration options, and available features. It covers the proper setup of a MariaDB connection, guidelines for connection management, and advanced behaviors such as ID retrieval and UUID handling. 8 | 9 | !!! info 10 | 11 | To configure the database dialect, extend the appropriate database connection class when defining your connection. You must choose the correct database type to ensure that the generated SQL queries follow the dialect expected by that database. 12 | 13 | !!! warning "Do not share connections between requests" 14 | 15 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 16 | 17 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 18 | 19 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 20 | 21 | ## Usage Example 22 | 23 | ```ts 24 | import { MariaDBConnection } from "ts-sql-query/connections/MariaDBConnection"; 25 | 26 | class DBConnection extends MariaDBConnection<'DBConnection'> { } 27 | ``` 28 | 29 | ## Insert ID Retrieval Strategies 30 | 31 | Starting from MariaDB 10.5, the `RETURNING` clause is supported for `INSERT` and `DELETE` statements. If you set this flag to `true`, `ts-sql-query` will use the `RETURNING` clause to retrieve the last inserted ID, instead of relying on the ID returned by the underlying connector after the query execution. 32 | 33 | ```ts 34 | import { MariaDBConnection } from "ts-sql-query/connections/MariaDBConnection"; 35 | 36 | class DBConnection extends MariaDBConnection<'DBConnection'> { 37 | protected alwaysUseReturningClauseWhenInsert = true 38 | } 39 | ``` 40 | 41 | ## UUID strategies 42 | 43 | `ts-sql-query` provides different strategies to handle UUID values in MariaDB. These strategies control how UUID values are represented in JavaScript and stored in the database. 44 | 45 | - `'uuid'` *(default strategy)*: UUIDs are treated as strings and stored using the native `UUID` column type. This requires MariaDB version 10.7 or higher. 46 | - `'string'`: UUIDs are treated as strings and stored in character-based columns such as `CHAR(36)`, `VARCHAR(36)`, or `TEXT`. This option can be used with older MariaDB versions or when avoiding the `UUID` type. 47 | 48 | You can configure the strategy by overriding the `uuidStrategy` field in your connection class: 49 | 50 | ```ts 51 | import { MariaDBConnection } from "ts-sql-query/connections/MariaDBConnection"; 52 | 53 | class DBConnection extends MariaDBConnection<'DBConnection'> { 54 | protected uuidStrategy = 'string' as const 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/configuration/supported-databases/mysql.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.59 4 | --- 5 | # MySQL 6 | 7 | This page describes how `ts-sql-query` integrates with **[MySQL](https://www.mysql.com)**, including dialect-specific behavior, configuration options, and available features. It covers the proper setup of a MySQL connection, guidelines for connection management, and advanced behaviors such as UUID handling. 8 | 9 | !!! info 10 | 11 | To configure the database dialect, extend the appropriate database connection class when defining your connection. You must choose the correct database type to ensure that the generated SQL queries follow the dialect expected by that database. 12 | 13 | !!! warning "Do not share connections between requests" 14 | 15 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 16 | 17 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 18 | 19 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 20 | 21 | ## Usage Example 22 | 23 | ```ts 24 | import { MySqlConnection } from "ts-sql-query/connections/MySqlConnection"; 25 | 26 | class DBConnection extends MySqlConnection<'DBConnection'> { } 27 | ``` 28 | 29 | ## UUID strategies 30 | 31 | `ts-sql-query` provides different strategies to handle UUID values in MariaDB. These strategies control how UUID values are represented in JavaScript and stored in the database. 32 | 33 | - `'uuid'` *(default strategy)*: UUIDs are treated as strings and stored using the native `BINARY` column type. This requires MySQL version 8 or higher. 34 | - `'string'`: UUIDs are treated as strings and stored in character-based columns such as `CHAR(36)`, `VARCHAR(36)`, or `TEXT`. This option can be used with older MySQL versions or when avoiding the `BINARY` type. 35 | 36 | You can configure the strategy by overriding the `uuidStrategy` field in your connection class: 37 | 38 | ```ts 39 | import { MySqlConnection } from "ts-sql-query/connections/MySqlConnection"; 40 | 41 | class DBConnection extends MySqlConnection<'DBConnection'> { 42 | protected uuidStrategy = 'string' as const 43 | } 44 | ``` 45 | 46 | ## Compatibility mode 47 | 48 | The compatibility mode avoids using the `WITH` clause to increase the compatibility with MySql 5. 49 | 50 | By default the compatibility mode is disabled. To enable the compatibility mode, you must set the `compatibilityMode` property of the connection to true. 51 | 52 | ```ts 53 | import { MySqlConnection } from "ts-sql-query/connections/MySqlConnection"; 54 | 55 | class DBConnection extends MySqlConnection<'DBConnection'> { 56 | protected compatibilityMode = true 57 | } 58 | ``` 59 | 60 | !!! warning 61 | 62 | When the compatibility mode is enabled recursive queries are not supported and you will get an error if you try to use them. 63 | -------------------------------------------------------------------------------- /docs/configuration/supported-databases/postgresql.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.59 4 | --- 5 | # PostgreSQL 6 | 7 | This page describes how `ts-sql-query` integrates with **[PostgreSQL](https://www.postgresql.org)**, including dialect-specific behavior, configuration options, and available features. It covers the proper setup of a PostgreSQL connection, guidelines for connection management, and advanced behaviors such as explicit typing. 8 | 9 | !!! info 10 | 11 | To configure the database dialect, extend the appropriate database connection class when defining your connection. You must choose the correct database type to ensure that the generated SQL queries follow the dialect expected by that database. 12 | 13 | !!! warning "Do not share connections between requests" 14 | 15 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 16 | 17 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 18 | 19 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 20 | 21 | ## Usage Example 22 | 23 | ```ts 24 | import { PostgreSqlConnection } from "ts-sql-query/connections/PostgreSqlConnection"; 25 | 26 | class DBConnection extends PostgreSqlConnection<'DBConnection'> { } 27 | ``` 28 | 29 | ## Explicit typing 30 | 31 | In some situations, PostgreSQL may be unable to infer the correct type of a parameter in a query. This often happens with untyped `NULL` values or when using generic placeholders. To ensure type safety and proper execution, you can explicitly cast the parameter type in the generated SQL. 32 | 33 | You can enforce explicit casting by overriding the `transformPlaceholder` method in your connection class. This method allows you to append a type annotation to the placeholder at the time of SQL generation. 34 | 35 | You may define your own cast rules or override the default behavior. For reference, see the default implementation in [`PostgreSqlConnection`](https://github.com/juanluispaz/ts-sql-query/blob/master/src/connections/PostgreSqlConnection.ts), or use the example below: 36 | 37 | ```ts 38 | import { PostgreSqlConnection } from "ts-sql-query/connections/PostgreSqlConnection"; 39 | 40 | class DBConnection extends PostgreSqlConnection<'DBConnection'> { 41 | protected transformPlaceholder( 42 | placeholder: string, 43 | type: string, 44 | _forceTypeCast: boolean, 45 | valueSentToDB: unknown 46 | ): string { 47 | return super.transformPlaceholder(placeholder, type, true, valueSentToDB) 48 | } 49 | } 50 | ``` 51 | 52 | !!! tip 53 | 54 | You can also enforce type casting using the `ForceTypeCast` adapter provided in `ts-sql-query/TypeAdapter`. For more advanced usage, see the section on [Global type adapter](../column-types.md#global-type-adapter). 55 | -------------------------------------------------------------------------------- /docs/configuration/supported-databases/sqlserver.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.59 4 | --- 5 | # SQL Server 6 | 7 | This page describes how `ts-sql-query` integrates with **[SQL Server](https://www.microsoft.com/en/sql-server)**, including dialect-specific behavior, configuration options, and available features. It covers the proper setup of a SQL Server connection, guidelines for connection management, and advanced behaviors such as UUID handling. 8 | 9 | !!! info 10 | 11 | To configure the database dialect, extend the appropriate database connection class when defining your connection. You must choose the correct database type to ensure that the generated SQL queries follow the dialect expected by that database. 12 | 13 | !!! warning "Do not share connections between requests" 14 | 15 | A `ts-sql-query` connection object — along with the query runner instances passed to its constructor — represents a **dedicated connection** to the database. 16 | 17 | Therefore, **you must not share the same connection object between concurrent HTTP requests**. Instead, create a new connection object for each request, along with its own query runners. 18 | 19 | Even if the query runner internally uses a connection pool, the `ts-sql-query` connection still represents a single active connection, acquired from the pool. It must be treated as such and never reused across requests. 20 | 21 | ## Usage Example 22 | 23 | ```ts 24 | import { SqlServerConnection } from "ts-sql-query/connections/SqlServerConnection"; 25 | 26 | class DBConnection extends SqlServerConnection<'DBConnection'> { } 27 | ``` 28 | 29 | !!! warning 30 | 31 | An empty string will be treated as a null value; if you need to allow empty string set the `allowEmptyString` property to true in the connection object. 32 | 33 | !!! tip 34 | 35 | SQL Server does not have a native boolean data type; `ts-sql-query` assumes that the boolean is represented by a bit where `0` is false, and `1` is true. All conversions are made automatically by `ts-sql-query`. In case you need a different way to represent a boolean, see [Custom booleans values](../../advanced/custom-booleans-values.md) for more information. 36 | 37 | ## UUID management 38 | 39 | In SQL Server, UUIDs are stored in columns of type `uniqueidentifier`, which preserve values in uppercase. If you prefer to convert them to lowercase during projection, you can override the `transformValueFromDB` method as shown below: 40 | 41 | ```ts 42 | import { SqlServerConnection } from "ts-sql-query/connections/SqlServerConnection"; 43 | 44 | class DBConnection extends SqlServerConnection<'DBConnection'> { 45 | protected transformValueFromDB(value: unknown, type: string): unknown { 46 | const result = super.transformValueFromDB(value, type); 47 | if (result && type === 'uuid') { 48 | return (result as string).toLowerCase(); 49 | } 50 | return result; 51 | } 52 | } 53 | ``` 54 | 55 | !!! tip 56 | 57 | If you use Prisma, this is done automatically. 58 | -------------------------------------------------------------------------------- /docs/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanluispaz/ts-sql-query/3fa4bc1bc13a30f45ff8b393cb381008e8f693b1/docs/demo.gif -------------------------------------------------------------------------------- /docs/extra.css: -------------------------------------------------------------------------------- 1 | .md-typeset h1 { 2 | color: var(--md-primary-fg-color); 3 | font-weight: 700; 4 | font-size: 2.2em; 5 | } 6 | 7 | .md-typeset h2 { 8 | color: var(--md-primary-fg-color); 9 | font-weight: 600; 10 | font-size: 1.6em; 11 | } 12 | 13 | .md-typeset h3 { 14 | color: var(--md-accent-fg-color); 15 | font-weight: 500; 16 | font-size: 1.3em; 17 | } 18 | 19 | .md-source__fact--stars { 20 | display: none; 21 | } 22 | 23 | .md-source__fact--forks { 24 | display: none; 25 | } 26 | 27 | .md-grid { 28 | max-width: unset; 29 | } 30 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 0.15 4 | --- 5 |
6 | 7 | logo 8 | 9 |

ts-sql-query

10 | 11 |

12 | Type-safe SQL query builder for TypeScript 13 |

14 | 15 | 23 | 24 |
25 | 26 | ## What is ts-sql-query? 27 | 28 | `ts-sql-query` is a type-safe SQL query builder for TypeScript. It allows you to write dynamic queries with full type safety — meaning that the TypeScript compiler can validate your queries at compile time. 29 | 30 | Type-safe SQL means that mistakes in your queries are caught at compile time. With `ts-sql-query`, you can safely refactor your database schema: any issues will be surfaced immediately by the compiler. 31 | 32 | !!! note "" 33 | 34 | `ts-sql-query` is _not_ an **ORM** — it focuses on full SQL control with type safety. 35 | 36 | ![](demo.gif) 37 | 38 | ## Why? 39 | 40 | There are many libraries available in JavaScript/TypeScript that allows querying a SQL database, but they are typically: 41 | 42 | - ORMs often limit direct control over SQL and can obscure database capabilities. 43 | - Many query builders rely on fragile string concatenation. 44 | - Some libraries lack proper type-safety integration. 45 | - Most tools make it hard to write truly dynamic SQL in a safe and expressive way. 46 | 47 | `ts-sql-query` addresses these issues by providing a type-safe, SQL-centric API, with rich support for building dynamic queries in a declarative style. 48 | 49 | For more details on the principles behind the library, see [Philosophy and Design Goals](./about/philosophy.md). 50 | 51 | ## Supported Databases 52 | 53 | - [MariaDB](./configuration/supported-databases/mariadb.md) 54 | - [MySQL](./configuration/supported-databases/mysql.md) 55 | - [Oracle](./configuration/supported-databases/oracle.md) 56 | - [PostgreSQL](./configuration/supported-databases/postgresql.md) 57 | - [SQLite](./configuration/supported-databases/sqlite.md) 58 | - [SQL Server](./configuration/supported-databases/sqlserver.md) 59 | 60 | `ts-sql-query` uses a unified dialect inspired by [SQLite](./configuration/supported-databases/sqlite.md) and [PostgreSQL](./configuration/supported-databases/postgresql.md), with naming conventions adapted to JavaScript. It automatically generates the correct SQL for your target database. 61 | 62 | ## Install 63 | 64 | Install with [npm](https://www.npmjs.com/): 65 | 66 | ```sh 67 | $ npm install --save ts-sql-query 68 | ``` 69 | 70 | `ts-sql-query` does not expose a global export. Instead, you should import specific modules as described in the documentation, depending on the features you need. Only the modules listed below are considered part of the public API — avoid importing from any other internal paths, as they may change without prior notice: 71 | 72 | - `ts-sql-query/Connection` 73 | - `ts-sql-query/Table` 74 | - `ts-sql-query/TypeAdapter` 75 | - `ts-sql-query/View` 76 | - `ts-sql-query/connections/*` 77 | - `ts-sql-query/extras/*` 78 | - `ts-sql-query/queryRunners/*` 79 | - `ts-sql-query/dynamicCondition` 80 | 81 | Any reference to a file outside of the previous list can change at any moment. 82 | 83 | ## Related projects 84 | 85 | - [ts-sql-codegen](https://github.com/lorefnon/ts-sql-codegen): Utility that generates table mapper classes for `ts-sql-query` by inspecting a database through [tbls](https://github.com/k1LoW/tbls). 86 | 87 | ## License 88 | 89 | [MIT](https://opensource.org/licenses/MIT) 90 | -------------------------------------------------------------------------------- /docs/keywords/delete.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 5 4 | --- 5 | # Delete Keywords 6 | 7 | This page lists the SQL keywords and clauses used in `DELETE` statements, along with their corresponding methods in `ts-sql-query`. It includes support for joins, conditional filters, returning deleted rows, and database-specific features like `USING` or `RETURNING INTO`. 8 | 9 | | SQL Keyword | ts-sql-query Equivalent | Notes | Link | 10 | |:------------------|:----------------------------------|:---------------------------------------------------------------------|:--------------------------------------------------------------| 11 | | DELETE FROM | connection `.deleteFrom(...)` | Starts a delete statement for a given table. | [Delete](../queries/delete.md), [Delete API](../api/delete.md) | 12 | | FROM | query `.from(...)` | Used to perform deletes with joins ([SQL Server](../configuration/supported-databases/sqlserver.md), [PostgreSQL](../configuration/supported-databases/postgresql.md), [MySQL](../configuration/supported-databases/mysql.md)). | [Delete using other tables or views](../queries/delete.md#delete-using-other-tables-or-views), [Delete API](../api/delete.md) | 13 | | INNER JOIN | query `.innerJoin(...)` | Inner join between tables. | [Delete using other tables or views](../queries/delete.md#delete-using-other-tables-or-views), [Delete API](../api/delete.md) | 14 | | JOIN | query `.join(...)` | Inner join between tables. | [Delete using other tables or views](../queries/delete.md#delete-using-other-tables-or-views), [Delete API](../api/delete.md) | 15 | | LIMIT | Not supported yet. Use a custom SQL fragment. | Restricts the number of rows that are deleted. | [Customizing a delete](../queries/sql-fragments.md#customizing-a-delete) | 16 | | LEFT JOIN | query `.leftJoin(...)` | Left outer join between tables. | [Delete API](../api/delete.md) | 17 | | LEFT OUTER JOIN | query `.leftOuterJoin(...)` | Left outer join between tables. | [Delete API](../api/delete.md) | 18 | | OUTPUT | query `.returning(...)` | Returns deleted rows or specific columns ([SQL Server](../configuration/supported-databases/sqlserver.md)). | [Delete returning](../queries/delete.md#delete-returning), [Delete API](../api/delete.md) | 19 | | RETURNING | query `.returning(...)` | Returns deleted rows or specific columns. | [Delete returning](../queries/delete.md#delete-returning), [Delete API](../api/delete.md) | 20 | | RETURNING INTO | query `.returning(...)` | Returns deleted rows or specific columns ([Oracle](../configuration/supported-databases/oracle.md)). | [Delete returning](../queries/delete.md#delete-returning), [Delete API](../api/delete.md) | 21 | | TOP | Not supported yet. Use a custom SQL fragment. | Restricts the number of rows that are deleted. | [Customizing a delete](../queries/sql-fragments.md#customizing-a-delete) | 22 | | USING | query `.using(...)` | Deletes using data from another table. | [Delete using other tables or views](../queries/delete.md#delete-using-other-tables-or-views), [Delete API](../api/delete.md) | 23 | | WHERE | query `.where(...)` | Filters which rows should be deleted. | [Delete](../queries/delete.md), [Delete API](../api/delete.md) | 24 | | WITH | query `.forUseInQueryAs(...)` | Common Table Expression (CTE) before DELETE. | [CTE usage](../queries/select.md#using-a-select-as-a-view-in-another-select-query-sql-with-clause) | 25 | | WITH RECURSIVE | query `.forUseInQueryAs(...)` | Recursive Common Table Expression before DELETE. | [Recursive select](../queries/recursive-select.md) | 26 | -------------------------------------------------------------------------------- /docs/keywords/update.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 5 4 | --- 5 | # Update Keywords 6 | 7 | This page lists the SQL keywords and clauses used in `UPDATE` statements, along with their corresponding methods in `ts-sql-query`. It covers setting column values, filtering rows, using joins, returning old and new values, and engine-specific clauses like `RETURNING` or `OUTPUT`. 8 | 9 | | SQL Keyword | ts-sql-query Equivalent | Notes | Link | 10 | |:-------------------------------------------|:-------------------------------------------|:--------------------------------------------------------------------------|:--------------------------------------------------------------| 11 | | DELETED | table `.oldValues()` | Refers to the previous value before an update. | [Update returning old values](../queries/update.md#update-returning-old-values), [Update API](../api/update.md) | 12 | | FROM | query `.from(...)` | Updates using data from another table. | [Update using other tables or views](../queries/update.md#update-using-other-tables-or-views), [Update API](../api/update.md) | 13 | | INNER JOIN | query `.innerJoin(...)` | Inner join between tables. | [Update API](../api/update.md) | 14 | | INSERTED | Managed automatically by `ts-sql-query` | Refers to the new value after an update. | | 15 | | JOIN | query `.join(...)` | Inner join between tables. | [Update API](../api/update.md) | 16 | | LEFT JOIN | query `.leftJoin(...)` | Left outer join between tables. | [Update API](../api/update.md) | 17 | | LEFT OUTER JOIN | query `.leftOuterJoin(...)` | Left outer join between tables. | [Update API](../api/update.md) | 18 | | LIMIT | Not supported yet. Use a custom SQL fragment. | Restricts the number of rows that are deleted. | [Customizing an update](../queries/sql-fragments.md#customizing-an-update) | 19 | | OUTPUT | query `.returning(...)` | Returns updated rows or specific columns ([SQL Server](../configuration/supported-databases/sqlserver.md)). | [Update returning](../queries/update.md#update-returning), [Update API](../api/update.md) | 20 | | RETURNING | query `.returning(...)` | Returns updated rows or specific columns. | [Update returning](../queries/update.md#update-returning), [Update API](../api/update.md) | 21 | | RETURNING INTO | query `.returning(...)` | Returns updated rows or specific columns ([Oracle](../configuration/supported-databases/oracle.md)). | [Update returning](../queries/update.md#update-returning), [Update API](../api/update.md) | 22 | | SET | query `.set({...})` | Set values. | [Update](../queries/update.md#general-update), [Update API](../api/update.md) | 23 | | TOP | Not supported yet. Use a custom SQL fragment. | Restricts the number of rows that are deleted. | [Customizing an update](../queries/sql-fragments.md#customizing-an-update) | 24 | | UPDATE | connection `.update(table)` | Updates records in a table. | [Update](../queries/update.md#general-update), [Update API](../api/update.md) | 25 | | WHERE | query `.where({...})` | Filters which rows should be updated. | [Update](../queries/update.md#general-update), [Update API](../api/update.md) | 26 | -------------------------------------------------------------------------------- /docs/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/queries/basic-query-structure.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 3 4 | --- 5 | # Basic query structure 6 | 7 | This section demonstrates how to construct and execute basic `SELECT` queries using `ts-sql-query`, providing examples of different selection methods and result-handling options. 8 | 9 | ## Select one row 10 | 11 | ```ts 12 | const customerId = 10; 13 | 14 | const customerWithId = connection.selectFrom(tCustomer) 15 | .where(tCustomer.id.equals(customerId)) 16 | .select({ 17 | id: tCustomer.id, 18 | firstName: tCustomer.firstName, 19 | lastName: tCustomer.lastName, 20 | birthday: tCustomer.birthday 21 | }) 22 | .executeSelectOne(); 23 | ``` 24 | 25 | The executed query is: 26 | ```sql 27 | select id as id, first_name as firstName, last_name as lastName, birthday as birthday 28 | from customer 29 | where id = $1 30 | ``` 31 | 32 | The parameters are: `[ 10 ]` 33 | 34 | The result type is: 35 | ```tsx 36 | const customerWithId: Promise<{ 37 | id: number; 38 | firstName: string; 39 | lastName: string; 40 | birthday?: Date; 41 | }> 42 | ``` 43 | 44 | The `executeSelectOne` returns one result, but if it is not found in the database an exception will be thrown. If you want to return the result when it is found or null when it is not found you must use the `executeSelectNoneOrOne` method. 45 | 46 | ## Projecting optional values 47 | 48 | By default, when an object is returned, optional values will be projected as optional properties in TypeScript, like `birthday?: Date`; when the value is absent, the property will not be set. However, you can change this behaviour so that all properties are always present, even if they contain `null`, like `birthday: Date | null` where, in case there is no value, the property will be set as null. To change this behaviour, call `projectingOptionalValuesAsNullable()` immediately after defining the projected columns with `select({...})`. 49 | 50 | ```ts 51 | const customerId = 10; 52 | 53 | const customerWithId = connection.selectFrom(tCustomer) 54 | .where(tCustomer.id.equals(customerId)) 55 | .select({ 56 | id: tCustomer.id, 57 | firstName: tCustomer.firstName, 58 | lastName: tCustomer.lastName, 59 | birthday: tCustomer.birthday 60 | }) 61 | .projectingOptionalValuesAsNullable() 62 | .executeSelectOne(); 63 | ``` 64 | 65 | The executed query is: 66 | ```sql 67 | select id as id, first_name as firstName, last_name as lastName, birthday as birthday 68 | from customer 69 | where id = $1 70 | ``` 71 | 72 | The parameters are: `[ 10 ]` 73 | 74 | The result type is: 75 | ```tsx 76 | const customerWithId: Promise<{ 77 | id: number; 78 | firstName: string; 79 | lastName: string; 80 | birthday: Date | null; 81 | }> 82 | ``` 83 | 84 | !!! note 85 | 86 | Projecting always-required properties that allow null values works in the same way with insert, update or deletes if you call `projectingOptionalValuesAsNullable()` immediately after `returning(...)`. This also applies to `connection.aggregateAsArray(...).projectingOptionalValuesAsNullable()`. 87 | 88 | ## Other options 89 | 90 | You can execute the query using: 91 | 92 | - `executeSelectNoneOrOne(): Promise`: execute the select query that returns one or no result from the database. In case of more than one result found, it throws and error with message 'Too many rows, expected only zero or one row'. 93 | - `executeSelectOne(): Promise`: execute the select query that returns one result from the database. If no result is returned by the database an exception will be thrown. 94 | - `executeSelectMany(): Promise`: execute the select query that returns zero or many results from the database 95 | - `executeSelectPage(): Promise<{ data: RESULT[], count: number }>`: executes a `SELECT` query that returns zero or more results. When using `executeSelectPage`, two queries are executed: one to retrieve the data and another to count the total number of matching rows (ignoring any `LIMIT` or `OFFSET`). 96 | - `executeSelectPage(extras: EXTRAS): Promise<{ data: RESULT[], count: number } & EXTRAS>`: execute the select query as a select page, but allows the resulting object to include additional custom properties. If the object provided by argument includes the property count, the query that count the data will be omitted and this value will be used. If the object provided by argument includes the property data, the query that extract the data will be omitted and this value will be used. 97 | 98 | Additionally, if you want to return the value of a single column, you can use `selectOneColumn(column)` instead of `select({...})`; or if you want to return `count(*)` as a single column, you can use `selectCountAll()`. 99 | -------------------------------------------------------------------------------- /docs/queries/select-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | search: 3 | boost: 3 4 | --- 5 | # Select page 6 | 7 | This feature provides a convenient way to retrieve paginated data along with the total number of matching rows. Internally, it executes two SQL queries: one for fetching the page of data and another for counting all rows that match the same filter conditions. This is especially useful for implementing efficient and consistent pagination in user interfaces. 8 | 9 | !!! success "Executed queries" 10 | 11 | The `executeSelectPage()` method runs the query **twice** behind the scenes: 12 | 13 | - The first execution fetches the **current page** of data, applying the specified `LIMIT`, `OFFSET`, and `ORDER BY` clauses. 14 | - The second execution runs the **same query without pagination**, in order to count the **total number of matching rows**. 15 | 16 | This dual-query strategy ensures consistent pagination, which is particularly useful for displaying data in user interfaces with accurate page controls (e.g., “Showing 21–30 of 146 results”). 17 | 18 | ```ts 19 | const customerName = 'Smi' 20 | 21 | const customerPageWithName = connection.selectFrom(tCustomer) 22 | .where( 23 | tCustomer.firstName.startsWithInsensitive(customerName) 24 | ).or( 25 | tCustomer.lastName.startsWithInsensitive(customerName) 26 | ).select({ 27 | id: tCustomer.id, 28 | firstName: tCustomer.firstName, 29 | lastName: tCustomer.lastName 30 | }) 31 | .orderBy('firstName') 32 | .orderBy('lastName') 33 | .limit(10) 34 | .offset(20) 35 | .executeSelectPage(); 36 | ``` 37 | 38 | The executed query to get the data is: 39 | ```sql 40 | select id as id, first_name as firstName, last_name as lastName 41 | from customer 42 | where first_name ilike ($1 || '%') 43 | or last_name ilike ($2 || '%') 44 | order by firstName, lastName 45 | limit $3 46 | offset $4 47 | ``` 48 | 49 | And its parameters are: `[ 'Smi', 'Smi', 10, 20 ]` 50 | 51 | The executed query to get the count is: 52 | ```sql 53 | select count(*) 54 | from customer 55 | where first_name ilike ($1 || '%') 56 | or last_name ilike ($2 || '%') 57 | ``` 58 | 59 | And its parameters are: `[ 'Smi', 'Smi' ]` 60 | 61 | The result type is: 62 | ```tsx 63 | const customerPageWithName: Promise<{ 64 | data: { 65 | id: number; 66 | firstName: string; 67 | lastName: string; 68 | }[]; 69 | count: number; 70 | }> 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs-material==9.6.11 -------------------------------------------------------------------------------- /docs/select.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juanluispaz/ts-sql-query/3fa4bc1bc13a30f45ff8b393cb381008e8f693b1/docs/select.pptx -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-sql-query", 3 | "version": "2.0.0-alpha.2", 4 | "description": "Type-safe SQL query builder like QueryDSL or JOOQ in Java or Linq in .Net for TypeScript with MariaDB, MySql, Oracle, PostgreSql, Sqlite and SqlServer support.", 5 | "license": "MIT", 6 | "author": "juanluispaz", 7 | "readmeFilename": "README.md", 8 | "keywords": [ 9 | "jooq", 10 | "querydsl", 11 | "typescript", 12 | "mariadb", 13 | "mysql", 14 | "oracle", 15 | "sqlite", 16 | "sqlserver", 17 | "sql-server", 18 | "sql server", 19 | "postgre sql", 20 | "postgresql", 21 | "typesql", 22 | "type-sql", 23 | "type sql", 24 | "type-safe sql", 25 | "type safe sql", 26 | "typesafe sql", 27 | "sql", 28 | "query builder", 29 | "query", 30 | "orm", 31 | "linq" 32 | ], 33 | "scripts": { 34 | "generate-prisma": "prisma generate --schema src/examples/prisma/postgresql.prisma; prisma generate --schema src/examples/prisma/mysql.prisma; prisma generate --schema src/examples/prisma/mariadb.prisma; prisma generate --schema src/examples/prisma/sqlite.prisma; prisma generate --schema src/examples/prisma/sqlserver.prisma", 35 | "copy-prisma": "cp -R ./src/examples/prisma ./dist/examples/prisma", 36 | "example": "ts-node src/exampleTests.ts", 37 | "all-examples": "sh ./scripts/run-all-examples.sh", 38 | "all-examples-arm": "sh ./scripts/run-all-examples-arm.sh", 39 | "all-examples-rosetta": "sh ./scripts/run-all-examples-rosetta.sh", 40 | "no-docker-examples": "sh ./scripts/run-no-docker-examples.sh", 41 | "coverage": "nyc sh ./scripts/run-all-examples.sh", 42 | "build": "rm -rf dist/*; tsc --version && tsc && npm run copy-prisma", 43 | "dist": "npm run build && cp LICENSE.md package.json README.md dist && cd dist && rm -Rf examples && npm publish", 44 | "dist-alpha": "npm run build && cp LICENSE.md package.json README.md dist && cd dist && rm -Rf examples && npm publish --tag alpha", 45 | "dist-beta": "npm run build && cp LICENSE.md package.json README.md dist && cd dist && rm -Rf examples && npm publish --tag beta", 46 | "test-dist": "npm run build && cp LICENSE.md package.json README.md dist && cd dist && rm -Rf examples && npm publish --dry-run", 47 | "docs": "mkdocs serve", 48 | "create-single-doc-file": "sh ./scripts/create-single-doc-file.sh" 49 | }, 50 | "repository": { 51 | "type": "git", 52 | "url": "https://github.com/juanluispaz/ts-sql-query.git" 53 | }, 54 | "bugs": { 55 | "url": "https://github.com/juanluispaz/ts-sql-query/issues" 56 | }, 57 | "homepage": "https://ts-sql-query.readthedocs.io/", 58 | "devDependencies": { 59 | "@prisma/client": "^6.6.0", 60 | "@sqlite.org/sqlite-wasm": "^3.49.1-build4", 61 | "@types/better-sqlite3": "^7.6.13", 62 | "@types/mssql":"^9.1.7", 63 | "@types/mysql": "^2.15.27", 64 | "@types/pg": "^8.11.13", 65 | "@types/oracledb":"^6.6.0", 66 | "@types/sqlite3": "^3.1.11", 67 | "better-sqlite3": "^11.9.1", 68 | "binary-uuid": "^2.0.3", 69 | "mariadb": "^3.4.1", 70 | "mssql": "^11.0.1", 71 | "mysql": "^2.18.1", 72 | "mysql2": "^3.14.0", 73 | "nyc": "^17.1.0", 74 | "oracledb": "^6.8.0", 75 | "pg": "^8.14.1", 76 | "postgres": "^3.4.5", 77 | "prisma": "^6.6.0", 78 | "sqlite": "^5.1.1", 79 | "sqlite3": "^5.1.7", 80 | "synchronous-promise": "^2.0.17", 81 | "ts-node": "^10.9.2", 82 | "typescript": "^5.8.3", 83 | "uuid": "^11.1.0" 84 | }, 85 | "devDependenciesToDisableOnRosetta": { 86 | "better-sqlite3": "^7.1.1" 87 | }, 88 | "dependencies": { 89 | "chained-error": "^1.0.0" 90 | }, 91 | "nyc": { 92 | "exclude": [ 93 | "src/examples/**" 94 | ], 95 | "reporter": [ 96 | "html" 97 | ], 98 | "include": [ 99 | "src/**" 100 | ] 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /scripts/create-single-doc-file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo '\n# index.md\n' > dist/doc.md 3 | cat docs/index.md >> dist/doc.md 4 | echo '\n# queries/basic-query-structure.md\n' >> dist/doc.md 5 | cat docs/queries/basic-query-structure.md >> dist/doc.md 6 | echo '\n# queries/dynamic-queries.md\n' >> dist/doc.md 7 | cat docs/queries/dynamic-queries.md >> dist/doc.md 8 | echo '\n# queries/select.md\n' >> dist/doc.md 9 | cat docs/queries/select.md >> dist/doc.md 10 | echo '\n# queries/select-page.md\n' >> dist/doc.md 11 | cat docs/queries/select-page.md >> dist/doc.md 12 | echo '\n# queries/recursive-select.md\n' >> dist/doc.md 13 | cat docs/queries/recursive-select.md >> dist/doc.md 14 | echo '\n# queries/complex-projections.md\n' >> dist/doc.md 15 | cat docs/queries/complex-projections.md >> dist/doc.md 16 | echo '\n# queries/aggregate-as-object-array.md\n' >> dist/doc.md 17 | cat docs/queries/aggregate-as-object-array.md >> dist/doc.md 18 | echo '\n# queries/insert.md\n' >> dist/doc.md 19 | cat docs/queries/insert.md >> dist/doc.md 20 | echo '\n# queries/update.md\n' >> dist/doc.md 21 | cat docs/queries/update.md >> dist/doc.md 22 | echo '\n# queries/delete.md\n' >> dist/doc.md 23 | cat docs/queries/delete.md >> dist/doc.md 24 | echo '\n# queries/transaction.md\n' >> dist/doc.md 25 | cat docs/queries/transaction.md >> dist/doc.md 26 | echo '\n# queries/sql-fragments.md\n' >> dist/doc.md 27 | cat docs/queries/sql-fragments.md >> dist/doc.md 28 | echo '\n# connection-tables-views.md\n' >> dist/doc.md 29 | cat docs/connection-tables-views.md >> dist/doc.md 30 | echo '\n# column-types.md\n' >> dist/doc.md 31 | cat docs/column-types.md >> dist/doc.md 32 | echo '\n# supported-operations.md\n' >> dist/doc.md 33 | cat docs/supported-operations.md >> dist/doc.md 34 | echo '\n# supported-databases.md\n' >> dist/doc.md 35 | cat docs/supported-databases.md >> dist/doc.md 36 | echo '\n# query-runners/recommended-query-runners.md\n' >> dist/doc.md 37 | cat docs/query-runners/recommended-query-runners.md >> dist/doc.md 38 | echo '\n# query-runners/additional-query-runners.md\n' >> dist/doc.md 39 | cat docs/query-runners/additional-query-runners.md >> dist/doc.md 40 | echo '\n# query-runners/general-purpose-query-runners.md\n' >> dist/doc.md 41 | cat docs/query-runners/general-purpose-query-runners.md >> dist/doc.md 42 | echo '\n# advanced-usage.md\n' >> dist/doc.md 43 | cat docs/advanced-usage.md >> dist/doc.md 44 | echo '\n# faqs-limimitations.md\n' >> dist/doc.md 45 | cat docs/faqs-limimitations.md >> dist/doc.md 46 | echo '\n# CHANGELOG.md\n' >> dist/doc.md 47 | cat docs/CHANGELOG.md >> dist/doc.md -------------------------------------------------------------------------------- /scripts/run-all-examples-rosetta.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Open terminal as x86 using rosetta 4 | # $ arch -x86_64 zsh --login 5 | # https://developer.apple.com/forums/thread/718666 6 | # https://vineethbharadwaj.medium.com/m1-mac-switching-terminal-between-x86-64-and-arm64-e45f324184d9 7 | 8 | # Install multiple node with different architecture 9 | # On terminal 10 | # $ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash 11 | # (Restart terminal) 12 | # $ nvm install v19.8.1 13 | # $ arch -x86_64 zsh --login 14 | # $ nvm install v19.8.0 15 | # $ nvm alias arm v19.8.1 16 | # $ nvm alias intel v19.8.0 17 | # https://www.jurnalanas.com/blog/node-js-mac-m1 18 | 19 | # Enable rosetta in docker (doesn't work, skip this) 20 | # https://levelup.gitconnected.com/docker-on-apple-silicon-mac-how-to-run-x86-containers-with-rosetta-2-4a679913a0d5 21 | 22 | # Install colima 23 | # https://blog.jdriven.com/2022/07/running-oracle-xe-on-apple-silicon/ 24 | # https://oralytics.com/2022/09/22/running-oracle-database-on-docker-on-apple-m1-chip/ 25 | 26 | # Download and uncompress instantclient-basic-macos: https://www.oracle.com/es/database/technologies/instant-client/macos-intel-x86-downloads.html 27 | # Execute the commmand in the uncompressed folder: xattr -d com.apple.quarantine * 28 | cp -R -X $PWD/../instantclient_19_8/* node_modules/oracledb/build/Release 29 | 30 | set -x #echo on 31 | 32 | node -p "if (process.arch !== 'x64') { process.exit(1) }" || { echo "not running in rosetta"; exit 1; } 33 | 34 | colima start --arch x86_64 --memory 4 35 | docker run --name ts-sql-query-oracle -d -p 1521:1521 -e ORACLE_PASSWORD=Oracle18 gvenzl/oracle-xe 36 | echo waiting 1 min of 7 min 37 | sleep 60 38 | echo waiting 2 min of 6 min 39 | sleep 60 40 | echo waiting 3 min of 6 min 41 | sleep 60 42 | echo waiting 4 min of 6 min 43 | sleep 60 44 | echo waiting 5 min of 6 min 45 | sleep 60 46 | echo waiting 6 min of 6 min 47 | sleep 60 48 | ts-node ./src/examples/OracleDBExample.ts 49 | echo waiting 7 min of 6 min 50 | sleep 60 51 | ts-node ./src/examples/OracleDBExample.ts || { docker stop ts-sql-query-oracle; docker rm ts-sql-query-oracle; colima stop; exit 1; } 52 | docker stop ts-sql-query-oracle 53 | docker rm ts-sql-query-oracle 54 | colima stop 55 | 56 | echo 'All examples ok (rosetta)' 57 | -------------------------------------------------------------------------------- /scripts/run-no-docker-examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x #echo on 4 | 5 | ts-node ./src/examples/documentation/Sqlite-compatibility.ts || exit 1 6 | ts-node ./src/examples/documentation/Sqlite-modern.ts || exit 1 7 | ts-node ./src/examples/documentation/PostgreSql.ts || exit 1 8 | ts-node ./src/examples/documentation/MySql.ts || exit 1 9 | ts-node ./src/examples/documentation/MySql-compatibility.ts || exit 1 10 | ts-node ./src/examples/documentation/MariaDB.ts || exit 1 11 | ts-node ./src/examples/documentation/MariaDB-modern.ts || exit 1 12 | ts-node ./src/examples/documentation/SqlServer.ts || exit 1 13 | ts-node ./src/examples/documentation/Oracle.ts || exit 1 14 | 15 | ts-node ./src/examples/SqliteExample.ts || exit 1 16 | ts-node ./src/examples/Sqlite3Example.ts || exit 1 17 | ts-node ./src/examples/BetterSqlite3Example.ts || exit 1 18 | ts-node ./src/examples/BetterSqlite3SynchronousExample.ts || exit 1 19 | ts-node ./src/examples/Sqlite3WasmOO1Example.ts || exit 1 20 | ts-node ./src/examples/Sqlite3WasmOO1SynchronousExample.ts || exit 1 21 | ts-node ./src/examples/PrismaSqliteExample.ts || exit 1 22 | 23 | echo 'All no docker examples ok' 24 | -------------------------------------------------------------------------------- /src/Connection.ts: -------------------------------------------------------------------------------- 1 | import type { AbstractConnection } from "./connections/AbstractConnection" 2 | import { NDB } from "./utils/sourceName" 3 | 4 | export type Connection = AbstractConnection 5 | export type { TransactionIsolationLevel } from "./connections/AbstractConnection" 6 | -------------------------------------------------------------------------------- /src/TypeAdapter.ts: -------------------------------------------------------------------------------- 1 | export interface DefaultTypeAdapter { 2 | transformValueFromDB(value: unknown, type: string): unknown 3 | transformValueToDB(value: unknown, type: string): unknown 4 | transformPlaceholder(placeholder: string, type: string, forceTypeCast: boolean, valueSentToDB: unknown): string 5 | } 6 | 7 | export interface TypeAdapter { 8 | transformValueFromDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown 9 | transformValueToDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown 10 | transformPlaceholder?(placeholder: string, type: string, forceTypeCast: boolean, valueSentToDB: unknown, next: DefaultTypeAdapter): string 11 | } 12 | 13 | export class CustomBooleanTypeAdapter implements TypeAdapter { 14 | readonly trueValue: number | string 15 | readonly falseValue: number | string 16 | 17 | constructor(trueValue: number, falseValue: number) 18 | constructor(trueValue: string, falseValue: string) 19 | constructor(trueValue: number | string, falseValue: number | string) { 20 | this.trueValue = trueValue 21 | this.falseValue = falseValue 22 | } 23 | 24 | transformValueFromDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown { 25 | return next.transformValueFromDB(value, type) 26 | } 27 | 28 | transformValueToDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown { 29 | return next.transformValueToDB(value, type) 30 | } 31 | } 32 | 33 | export class ForceTypeCast implements TypeAdapter { 34 | 35 | transformValueFromDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown { 36 | return next.transformValueFromDB(value, type) 37 | } 38 | 39 | transformValueToDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown { 40 | return next.transformValueToDB(value, type) 41 | } 42 | 43 | transformPlaceholder(placeholder: string, type: string, _forceTypeCast: boolean, valueSentToDB: unknown, next: DefaultTypeAdapter): string { 44 | return next.transformPlaceholder(placeholder, type, true, valueSentToDB) 45 | } 46 | } -------------------------------------------------------------------------------- /src/complexProjections/asLeftJoin.ts: -------------------------------------------------------------------------------- 1 | import type { AnyValueSource, IValueSource, RemapValueSourceTypeWithOptionalType } from "../expressions/values" 2 | import type { UsableKeyOf } from '../utils/objectUtils' 3 | import type { NSource } from "../utils/sourceName" 4 | 5 | /* 6 | * Reasign all columns to in a with view 7 | */ 8 | 9 | export type ColumnsForLeftJoin = 10 | { [K in UsableKeyOf]: 11 | COLUMNS[K] extends AnyValueSource | undefined 12 | ? RemapValueSourceTypeWithOptionalType> 13 | : ColumnsForLeftJoin2 14 | } 15 | 16 | type ColumnsForLeftJoin2 = 17 | { [K in UsableKeyOf]: 18 | COLUMNS[K] extends AnyValueSource | undefined 19 | ? RemapValueSourceTypeWithOptionalType> 20 | : ColumnsForLeftJoin3 21 | } 22 | 23 | type ColumnsForLeftJoin3 = 24 | { [K in UsableKeyOf]: 25 | COLUMNS[K] extends AnyValueSource | undefined 26 | ? RemapValueSourceTypeWithOptionalType> 27 | : ColumnsForLeftJoin4 28 | } 29 | 30 | type ColumnsForLeftJoin4 = 31 | { [K in UsableKeyOf]: 32 | COLUMNS[K] extends AnyValueSource | undefined 33 | ? RemapValueSourceTypeWithOptionalType> 34 | : ColumnsForLeftJoin5 35 | } 36 | 37 | type ColumnsForLeftJoin5 = 38 | { [K in UsableKeyOf]: 39 | COLUMNS[K] extends AnyValueSource | undefined 40 | ? RemapValueSourceTypeWithOptionalType> 41 | : COLUMNS[K] // Stop recursion 42 | } 43 | 44 | type OptionalTypeForLeftJoin = 45 | TYPE extends IValueSource ? ( 46 | 'required' extends OPTIONAL_TYPE 47 | ? 'originallyRequired' 48 | : OPTIONAL_TYPE 49 | ) : never -------------------------------------------------------------------------------- /src/complexProjections/asWithView.ts: -------------------------------------------------------------------------------- 1 | import type { AnyValueSource, IValueSource, RemapValueSourceTypeWithOptionalType } from "../expressions/values" 2 | import type { Expand, UsableKeyOf } from '../utils/objectUtils' 3 | import type { NSource } from "../utils/sourceName" 4 | 5 | /* 6 | * Reasign all columns to in a with view 7 | */ 8 | 9 | export type ColumnsForWithView = Expand< 10 | { [K in UsableKeyOf]: 11 | COLUMNS[K] extends AnyValueSource | undefined 12 | ? RemapValueSourceTypeWithOptionalType> 13 | : ColumnsForWithView2 14 | } 15 | > 16 | 17 | type ColumnsForWithView2 = Expand< 18 | { [K in UsableKeyOf]: 19 | COLUMNS[K] extends AnyValueSource | undefined 20 | ? RemapValueSourceTypeWithOptionalType> 21 | : ColumnsForWithView3 22 | } 23 | > 24 | 25 | type ColumnsForWithView3 = Expand< 26 | { [K in UsableKeyOf]: 27 | COLUMNS[K] extends AnyValueSource | undefined 28 | ? RemapValueSourceTypeWithOptionalType> 29 | : ColumnsForWithView4 30 | } 31 | > 32 | 33 | type ColumnsForWithView4 = Expand< 34 | { [K in UsableKeyOf]: 35 | COLUMNS[K] extends AnyValueSource | undefined 36 | ? RemapValueSourceTypeWithOptionalType> 37 | : ColumnsForWithView5 38 | } 39 | > 40 | 41 | type ColumnsForWithView5 = Expand< 42 | { [K in UsableKeyOf]: 43 | COLUMNS[K] extends AnyValueSource | undefined 44 | ? RemapValueSourceTypeWithOptionalType> 45 | : COLUMNS[K] // Stop recursion 46 | } 47 | > 48 | 49 | type OptionalTypeForWith = 50 | TYPE extends IValueSource ? ( 51 | 'required' extends OPTIONAL_TYPE 52 | ? 'required' 53 | : 'optional' 54 | ) : never -------------------------------------------------------------------------------- /src/complexProjections/compound.ts: -------------------------------------------------------------------------------- 1 | import type { AnyValueSource, IValueSource, OptionalTypeRequiredOrAny, RemapIValueSourceTypeWithOptionalType } from "../expressions/values" 2 | import type { UsableKeyOf } from '../utils/objectUtils' 3 | import type { NSource } from "../utils/sourceName" 4 | 5 | /* 6 | * Used as type mark in compoundable select, for union, union all, intersect, intersect all, except, except all, minus, minus all; 7 | * as well in recursive union and recursive union all. 8 | * 9 | * This allows to ensure the types has excatly the same columns. 10 | */ 11 | 12 | export type ColumnsForCompound = COLUMNS extends AnyValueSource 13 | ? RemapIValueSourceTypeWithOptionalType> 14 | : ColumnsForCompound1 15 | 16 | type ColumnsForCompound1 = 17 | { [K in UsableKeyOf]: 18 | COLUMNS[K] extends AnyValueSource | undefined 19 | ? RemapIValueSourceTypeWithOptionalType> 20 | : ColumnsForCompound2 21 | } 22 | 23 | type ColumnsForCompound2 = 24 | { [K in UsableKeyOf]: 25 | COLUMNS[K] extends AnyValueSource | undefined 26 | ? RemapIValueSourceTypeWithOptionalType> 27 | : ColumnsForCompound3 28 | } 29 | 30 | type ColumnsForCompound3 = 31 | { [K in UsableKeyOf]: 32 | COLUMNS[K] extends AnyValueSource | undefined 33 | ? RemapIValueSourceTypeWithOptionalType> 34 | : ColumnsForCompound4 35 | } 36 | 37 | type ColumnsForCompound4 = 38 | { [K in UsableKeyOf]: 39 | COLUMNS[K] extends AnyValueSource | undefined 40 | ? RemapIValueSourceTypeWithOptionalType> 41 | : ColumnsForCompound5 42 | } 43 | 44 | type ColumnsForCompound5 = 45 | { [K in UsableKeyOf]: 46 | COLUMNS[K] extends AnyValueSource | undefined 47 | ? RemapIValueSourceTypeWithOptionalType> 48 | : COLUMNS[K] // Stop recursion 49 | } 50 | 51 | type CompoundColumnOptionalType = 52 | COLUMN extends IValueSource 53 | ? OptionalTypeRequiredOrAny 54 | : never -------------------------------------------------------------------------------- /src/complexProjections/tableAlias.ts: -------------------------------------------------------------------------------- 1 | import type { AnyValueSource, RemapValueSourceType } from "../expressions/values" 2 | import type { UsableKeyOf } from '../utils/objectUtils' 3 | import type { NSource } from "../utils/sourceName" 4 | 5 | /* 6 | * Reasign all columns to a new table/view alias 7 | */ 8 | 9 | export type ColumnsForAlias = 10 | { [K in UsableKeyOf]: 11 | COLUMNS[K] extends AnyValueSource | undefined 12 | ? RemapValueSourceType 13 | : ColumnsForAlias2 14 | } 15 | 16 | type ColumnsForAlias2 = 17 | { [K in UsableKeyOf]: 18 | COLUMNS[K] extends AnyValueSource | undefined 19 | ? RemapValueSourceType 20 | : ColumnsForAlias3 21 | } 22 | 23 | type ColumnsForAlias3 = 24 | { [K in UsableKeyOf]: 25 | COLUMNS[K] extends AnyValueSource | undefined 26 | ? RemapValueSourceType 27 | : ColumnsForAlias4 28 | } 29 | 30 | type ColumnsForAlias4 = 31 | { [K in UsableKeyOf]: 32 | COLUMNS[K] extends AnyValueSource | undefined 33 | ? RemapValueSourceType 34 | : ColumnsForAlias5 35 | } 36 | 37 | type ColumnsForAlias5 = 38 | { [K in UsableKeyOf]: 39 | COLUMNS[K] extends AnyValueSource | undefined 40 | ? RemapValueSourceType 41 | : COLUMNS[K] // Stop recursion 42 | } 43 | -------------------------------------------------------------------------------- /src/connections/MariaDBConnection.ts: -------------------------------------------------------------------------------- 1 | import type { NConnection } from "../utils/sourceName" 2 | import type { QueryRunner } from "../queryRunners/QueryRunner" 3 | import { MariaDBSqlBuilder } from "../sqlBuilders/MariaDBSqlBuilder" 4 | import { AbstractConnection, TransactionIsolationLevel } from "./AbstractConnection" 5 | 6 | export abstract class MariaDBConnection extends AbstractConnection> { 7 | 8 | protected uuidStrategy: 'string' | 'uuid' = 'uuid' 9 | 10 | /** 11 | * MariaBD 10.5 added support to the returning clause when insert or delete. 12 | * If you set this flag to true, the insert returning last inserted id will 13 | * generate the returning clause instead of use the last inserted id provided 14 | * by the connector after the execution of the query. 15 | */ 16 | protected alwaysUseReturningClauseWhenInsert: boolean = false 17 | 18 | constructor(queryRunner: QueryRunner, sqlBuilder = new MariaDBSqlBuilder()) { 19 | super(queryRunner, sqlBuilder) 20 | queryRunner.useDatabase('mariaDB') 21 | } 22 | 23 | isolationLevel(level: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable', accessMode?: 'read write' | 'read only'): TransactionIsolationLevel 24 | isolationLevel(accessMode: 'read write' | 'read only'): TransactionIsolationLevel 25 | isolationLevel(level: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable' | 'read write' | 'read only', accessMode?: 'read write' | 'read only'): TransactionIsolationLevel { 26 | if (level === 'read write' || level === 'read only') { 27 | return [undefined, accessMode] as any 28 | } 29 | if (accessMode) { 30 | return [level, accessMode] as any 31 | } 32 | return [level] as any 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/connections/MySqlConnection.ts: -------------------------------------------------------------------------------- 1 | import type { NConnection } from "../utils/sourceName" 2 | import type { QueryRunner } from "../queryRunners/QueryRunner" 3 | import { MySqlSqlBuilder } from "../sqlBuilders/MySqlSqlBuilder" 4 | import { AbstractConnection, TransactionIsolationLevel } from "./AbstractConnection" 5 | 6 | export abstract class MySqlConnection extends AbstractConnection> { 7 | 8 | protected uuidStrategy: 'string' | 'binary' = 'binary' 9 | 10 | /** 11 | * The compatibility mode try to maximize the compatibility with older versions of MySQL (MySQL 5) 12 | * 13 | * The syntax avoided are: 14 | * - With clause, instead the query is directly included in the from 15 | * 16 | * Note: Recursive queries are not supported 17 | */ 18 | protected compatibilityMode: boolean = false 19 | 20 | constructor(queryRunner: QueryRunner, sqlBuilder: MySqlSqlBuilder = new MySqlSqlBuilder()) { 21 | super(queryRunner, sqlBuilder) 22 | queryRunner.useDatabase('mySql') 23 | } 24 | 25 | isolationLevel(level: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable', accessMode?: 'read write' | 'read only'): TransactionIsolationLevel 26 | isolationLevel(accessMode: 'read write' | 'read only'): TransactionIsolationLevel 27 | isolationLevel(level: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable' | 'read write' | 'read only', accessMode?: 'read write' | 'read only'): TransactionIsolationLevel { 28 | if (level === 'read write' || level === 'read only') { 29 | return [undefined, accessMode] as any 30 | } 31 | if (accessMode) { 32 | return [level, accessMode] as any 33 | } 34 | return [level] as any 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/connections/OracleConnection.ts: -------------------------------------------------------------------------------- 1 | import type { NConnection } from "../utils/sourceName" 2 | import type { QueryRunner } from "../queryRunners/QueryRunner" 3 | import { OracleSqlBuilder } from "../sqlBuilders/OracleSqlBuilder" 4 | import { AbstractAdvancedConnection } from "./AbstractAdvancedConnection" 5 | import { TransactionIsolationLevel } from "./AbstractConnection" 6 | 7 | export abstract class OracleConnection extends AbstractAdvancedConnection> { 8 | 9 | protected uuidStrategy: 'string' | 'custom-functions' = 'custom-functions' 10 | 11 | constructor(queryRunner: QueryRunner, sqlBuilder = new OracleSqlBuilder()) { 12 | super(queryRunner, sqlBuilder) 13 | queryRunner.useDatabase('oracle') 14 | } 15 | 16 | isolationLevel(level: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable'): TransactionIsolationLevel 17 | isolationLevel(accessMode: 'read write' | 'read only'): TransactionIsolationLevel 18 | isolationLevel(level: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable' | 'read write' | 'read only'): TransactionIsolationLevel { 19 | if (level === 'read write' || level === 'read only') { 20 | return [undefined, level] as any 21 | } 22 | return [level] as any 23 | } 24 | 25 | protected transformValueToDB(value: unknown, type: string): unknown { 26 | if (type === 'boolean' && typeof value === 'boolean') { 27 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_coercion 28 | return Number(value); 29 | } 30 | return super.transformValueToDB(value, type) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/connections/PostgreSqlConnection.ts: -------------------------------------------------------------------------------- 1 | import type { NConnection } from "../utils/sourceName" 2 | import type { QueryRunner } from "../queryRunners/QueryRunner" 3 | import { PostgreSqlSqlBuilder } from "../sqlBuilders/PostgreSqlSqlBuilder" 4 | import { AbstractAdvancedConnection } from "./AbstractAdvancedConnection" 5 | import { TransactionIsolationLevel } from "./AbstractConnection" 6 | 7 | export abstract class PostgreSqlConnection extends AbstractAdvancedConnection> { 8 | 9 | constructor(queryRunner: QueryRunner, sqlBuilder = new PostgreSqlSqlBuilder()) { 10 | super(queryRunner, sqlBuilder) 11 | queryRunner.useDatabase('postgreSql') 12 | } 13 | 14 | isolationLevel(level: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable', accessMode?: 'read write' | 'read only'): TransactionIsolationLevel 15 | isolationLevel(accessMode: 'read write' | 'read only'): TransactionIsolationLevel 16 | isolationLevel(level: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable' | 'read write' | 'read only', accessMode?: 'read write' | 'read only'): TransactionIsolationLevel { 17 | if (level === 'read write' || level === 'read only') { 18 | return [undefined, accessMode] as any 19 | } 20 | if (accessMode) { 21 | return [level, accessMode] as any 22 | } 23 | return [level] as any 24 | } 25 | 26 | protected transformPlaceholder(placeholder: string, type: string, forceTypeCast: boolean, valueSentToDB: unknown): string { 27 | if (!forceTypeCast) { 28 | return super.transformPlaceholder(placeholder, type, forceTypeCast, valueSentToDB) 29 | } 30 | 31 | switch (type) { 32 | case 'boolean': 33 | return placeholder + '::bool' 34 | case 'int': 35 | return placeholder + '::int4' 36 | case 'bigint': 37 | return placeholder + '::int8' 38 | case 'stringInt': 39 | return placeholder + '::int8' 40 | case 'double': 41 | return placeholder + '::float8' 42 | case 'stringDouble': 43 | return placeholder + '::float8' 44 | case 'string': 45 | return placeholder + '::text' 46 | case 'uuid': 47 | return placeholder + '::uuid' 48 | case 'localDate': 49 | return placeholder + '::date' 50 | case 'localTime': 51 | return placeholder + '::timestamp::time' 52 | case 'localDateTime': 53 | return placeholder + '::timestamp' 54 | } 55 | 56 | if (typeof valueSentToDB === 'bigint') { 57 | return placeholder + '::int8' 58 | } 59 | if (typeof valueSentToDB === 'number') { 60 | if (Number.isInteger(valueSentToDB)) { 61 | if (valueSentToDB >= -2147483648 && valueSentToDB <= 2147483647) { 62 | // Int32 number 63 | return placeholder + '::int4' 64 | } else { 65 | return placeholder + '::int8' 66 | } 67 | } else { 68 | return placeholder + '::float8' 69 | } 70 | } 71 | 72 | return placeholder 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/connections/SqlServerConnection.ts: -------------------------------------------------------------------------------- 1 | import type { NConnection } from "../utils/sourceName" 2 | import type { QueryRunner } from "../queryRunners/QueryRunner" 3 | import { SqlServerSqlBuilder } from "../sqlBuilders/SqlServerSqlBuilder" 4 | import { AbstractAdvancedConnection } from "./AbstractAdvancedConnection" 5 | import type { TransactionIsolationLevel } from "./AbstractConnection" 6 | 7 | export abstract class SqlServerConnection extends AbstractAdvancedConnection> { 8 | 9 | constructor(queryRunner: QueryRunner, sqlBuilder = new SqlServerSqlBuilder()) { 10 | super(queryRunner, sqlBuilder) 11 | queryRunner.useDatabase('sqlServer') 12 | } 13 | 14 | isolationLevel(level: 'read uncommitted' | 'read committed' | 'repeatable read' | 'snapshot' | 'serializable'): TransactionIsolationLevel { 15 | return [level] as any 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/connections/SqliteConfiguration.ts: -------------------------------------------------------------------------------- 1 | export type SqliteDateTimeFormat = 'localdate as text' | 'localdate as text using T separator' 2 | | 'UTC as text' | 'UTC as text using T separator' | 'UTC as text using Z timezone' | 'UTC as text using T separator and Z timezone' 3 | | 'Julian day as real number' | 'Unix time seconds as integer' | 'Unix time milliseconds as integer' 4 | 5 | export type SqliteDateTimeFormatType = 'date' | 'time' | 'dateTime' -------------------------------------------------------------------------------- /src/examples/assertEquals.ts: -------------------------------------------------------------------------------- 1 | export function assertEquals(first: any, second: any) { 2 | const result = verifyEquals(first, second, '') 3 | if (result) { 4 | throw new Error(result) 5 | } 6 | } 7 | 8 | function verifyEquals(first: any, second: any, path: string = '') : string | undefined { 9 | if (Array.isArray(first)) { 10 | if (!Array.isArray(second)) { 11 | return 'not equals (not same type array)\nFirst: ' + JSON.stringify(first) + '\nSecond: ' + JSON.stringify(second) + '\nPath: ' + path 12 | } 13 | if (first.length !== second.length) { 14 | return 'not equals (not same length)\nFirst: ' + JSON.stringify(first) + '\nSecond: ' + JSON.stringify(second) + '\nPath: ' + path 15 | } 16 | for (let index = 0, length = first.length; index < length; index++) { 17 | const result = verifyEquals(first[index], second[index], path + '[' + index + ']') 18 | if (result) { 19 | return result 20 | } 21 | } 22 | } else if (first instanceof Date) { 23 | if (!(second instanceof Date)) { 24 | return 'not equals (not same type date)\nFirst: ' + JSON.stringify(first) + '\nSecond: ' + JSON.stringify(second) + '\nPath: ' + path 25 | } 26 | if ((second as any).___type___ === 'localDate' || (first as any).___type___ === 'localDate') { 27 | if (!(first.getFullYear() === second.getFullYear() && first.getMonth() === second.getMonth() && first.getDate() === second.getDate())) { 28 | return 'not equals (not same localDate)\nFirst: ' + JSON.stringify(first) + '\nSecond: ' + JSON.stringify(second) + '\nPath: ' + path 29 | } 30 | } else if ((second as any).___type___ === 'localTime' || (first as any).___type___ === 'localTime') { 31 | if (!(first.getHours() === second.getHours() && first.getMinutes() === second.getMinutes() && first.getSeconds() === second.getSeconds() && first.getMilliseconds() === second.getMilliseconds())) { 32 | return 'not equals (not same localTime)\nFirst: ' + JSON.stringify(first) + '\nSecond: ' + JSON.stringify(second) + '\nPath: ' + path 33 | } 34 | } else if (first.getTime() !== second.getTime()) { 35 | return 'not equals (date)\nFirst: ' + JSON.stringify(first) + '\nSecond: ' + JSON.stringify(second) + '\nPath: ' + path 36 | } 37 | } else if (first === null || first === undefined) { 38 | if (!(second === null || second === undefined)) { 39 | return 'not equals (null or undefined)\nFirst: ' + JSON.stringify(first) + '\nSecond: ' + JSON.stringify(second) + '\nPath: ' + path 40 | } 41 | } else if (typeof first === 'object') { 42 | if (typeof second !== 'object') { 43 | return 'not equals (not same object type)\nFirst: ' + JSON.stringify(first) + '\nSecond: ' + JSON.stringify(second) + '\nPath: ' + path 44 | } 45 | for (let key in first) { 46 | const result = verifyEquals(first[key], second[key], path + '.' + key) 47 | if (result) { 48 | return result 49 | } 50 | } 51 | for (let key in second) { 52 | const result = verifyEquals(first[key], second[key], path + '.' + key) 53 | if (result) { 54 | return result 55 | } 56 | } 57 | } else if (first !== second) { 58 | if (typeof first === 'string') { 59 | if (typeof second === 'string') { 60 | return 'not equals (===)\nFirst: ' + first + '\nSecond: ' + second + '\nPath: ' + path 61 | } else { 62 | return 'not equals (===)\nFirst: ' + first + '\nSecond: ' + JSON.stringify(second) + '\nPath: ' + path 63 | } 64 | } else { 65 | if (typeof second === 'string') { 66 | return 'not equals (===)\nFirst: ' + JSON.stringify(first) + '\nSecond: ' + second + '\nPath: ' + path 67 | } else { 68 | return 'not equals (===)\nFirst: ' + JSON.stringify(first) + '\nSecond: ' + JSON.stringify(second) + '\nPath: ' + path 69 | } 70 | } 71 | } 72 | return undefined 73 | } -------------------------------------------------------------------------------- /src/examples/prisma/mariadb.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "mysql" 3 | url = "mysql://root:my-secret-pw@localhost/test" 4 | } 5 | 6 | generator client { 7 | provider = "prisma-client-js" 8 | output = "./generated/mariadb" 9 | } 10 | 11 | model Company { 12 | id Int @id @default(autoincrement()) @db.Int 13 | name String @db.VarChar(100) 14 | Customer Customer[] 15 | 16 | @@map("company") 17 | } 18 | 19 | model Customer { 20 | id Int @id @default(autoincrement()) @db.Int 21 | firstName String @map("first_name") @db.VarChar(100) 22 | lastName String @map("last_name") @db.VarChar(100) 23 | birthday DateTime @db.Date 24 | company Company @relation(fields: [companyId], references: [id]) 25 | companyId Int @map("company_id") 26 | 27 | @@map("customer") 28 | } 29 | -------------------------------------------------------------------------------- /src/examples/prisma/mysql.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "mysql" 3 | url = "mysql://root:my-secret-pw@localhost/sys" 4 | } 5 | 6 | generator client { 7 | provider = "prisma-client-js" 8 | output = "./generated/mysql" 9 | } 10 | 11 | model Company { 12 | id Int @id @default(autoincrement()) @db.Int 13 | name String @db.VarChar(100) 14 | Customer Customer[] 15 | 16 | @@map("company") 17 | } 18 | 19 | model Customer { 20 | id Int @id @default(autoincrement()) @db.Int 21 | firstName String @map("first_name") @db.VarChar(100) 22 | lastName String @map("last_name") @db.VarChar(100) 23 | birthday DateTime @db.Date 24 | company Company @relation(fields: [companyId], references: [id]) 25 | companyId Int @map("company_id") 26 | 27 | @@map("customer") 28 | } 29 | -------------------------------------------------------------------------------- /src/examples/prisma/postgresql.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "postgresql" 3 | url = "postgresql://postgres:mysecretpassword@localhost:5432/postgres" 4 | } 5 | 6 | generator client { 7 | provider = "prisma-client-js" 8 | output = "./generated/postgresql" 9 | } 10 | 11 | model Company { 12 | id Int @id @default(autoincrement()) @db.Integer 13 | name String @db.VarChar(100) 14 | Customer Customer[] 15 | 16 | @@map("company") 17 | } 18 | 19 | model Customer { 20 | id Int @id @default(autoincrement()) @db.Integer 21 | firstName String @map("first_name") @db.VarChar(100) 22 | lastName String @map("last_name") @db.VarChar(100) 23 | birthday DateTime @db.Date 24 | company Company @relation(fields: [companyId], references: [id]) 25 | companyId Int @map("company_id") 26 | 27 | @@map("customer") 28 | } 29 | -------------------------------------------------------------------------------- /src/examples/prisma/sqlite.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "sqlite" 3 | url = "file:./generated/prismasqlitetest.db" 4 | } 5 | 6 | generator client { 7 | provider = "prisma-client-js" 8 | output = "./generated/sqlite" 9 | } 10 | 11 | model Company { 12 | id Int @id @default(autoincrement()) 13 | name String 14 | Customer Customer[] 15 | 16 | @@map("company") 17 | } 18 | 19 | model Customer { 20 | id Int @id @default(autoincrement()) 21 | firstName String @map("first_name") 22 | lastName String @map("last_name") 23 | birthday DateTime 24 | company Company @relation(fields: [companyId], references: [id]) 25 | companyId Int @map("company_id") 26 | 27 | @@map("customer") 28 | } 29 | -------------------------------------------------------------------------------- /src/examples/prisma/sqlserver.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "sqlserver" 3 | url = "sqlserver://localhost:1433;user=sa;password=yourStrong(!)Password;TrustServerCertificate=true" 4 | } 5 | 6 | generator client { 7 | provider = "prisma-client-js" 8 | output = "./generated/sqlserver" 9 | } 10 | 11 | model Company { 12 | id Int @id @default(autoincrement()) @db.Int 13 | name String @db.VarChar(100) 14 | Customer Customer[] 15 | 16 | @@map("company") 17 | } 18 | 19 | model Customer { 20 | id Int @id @default(autoincrement()) @db.Int 21 | firstName String @map("first_name") @db.VarChar(100) 22 | lastName String @map("last_name") @db.VarChar(100) 23 | birthday DateTime @db.Date 24 | company Company @relation(fields: [companyId], references: [id]) 25 | companyId Int @map("company_id") 26 | 27 | @@map("customer") 28 | } 29 | -------------------------------------------------------------------------------- /src/expressions/Default.ts: -------------------------------------------------------------------------------- 1 | import type { ToSql, SqlBuilder } from "../sqlBuilders/SqlBuilder" 2 | import { type } from "../utils/symbols" 3 | 4 | export interface Default { 5 | [type]: 'default' 6 | } 7 | 8 | export class DefaultImpl implements Default, ToSql { 9 | [type]!: 'default' 10 | __toSql(SqlBuilder: SqlBuilder, params: any[]): string { 11 | return SqlBuilder._default(params) 12 | } 13 | __toSqlForCondition(SqlBuilder: SqlBuilder, params: any[]): string { 14 | return SqlBuilder._default(params) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/expressions/sequence.ts: -------------------------------------------------------------------------------- 1 | export interface Sequence { 2 | nextValue(): T 3 | currentValue(): T 4 | } -------------------------------------------------------------------------------- /src/internal/ProxyTypeAdapter.ts: -------------------------------------------------------------------------------- 1 | import { DefaultTypeAdapter, TypeAdapter } from "../TypeAdapter" 2 | 3 | export class ProxyTypeAdapter implements TypeAdapter { 4 | typeAdapter: TypeAdapter 5 | 6 | constructor(typeAdapter: TypeAdapter) { 7 | this.typeAdapter = typeAdapter 8 | } 9 | 10 | transformValueFromDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown { 11 | return this.typeAdapter.transformValueFromDB(value, type, next) 12 | } 13 | 14 | transformValueToDB(value: unknown, type: string, next: DefaultTypeAdapter): unknown { 15 | return this.typeAdapter.transformValueToDB(value, type, next) 16 | } 17 | } -------------------------------------------------------------------------------- /src/internal/RawFragmentImpl.ts: -------------------------------------------------------------------------------- 1 | import { AnyValueSource, IExecutableDeleteQuery, IExecutableInsertQuery, IExecutableSelectQuery, IExecutableUpdateQuery } from "../expressions/values" 2 | import { SqlBuilder, ToSql } from "../sqlBuilders/SqlBuilder" 3 | import { DBColumn } from "../utils/Column" 4 | import { AnyTableOrView, HasAddWiths, HasIsValue, IWithView, __addWiths, __getOldValues, __getValuesForInsert, __isAllowed, __registerRequiredColumn, __registerTableOrView } from "../utils/ITableOrView" 5 | import type { RawFragment } from "../utils/RawFragment" 6 | import { source, type } from "../utils/symbols" 7 | 8 | export class RawFragmentImpl implements RawFragment, HasAddWiths, ToSql { 9 | [type]!: "rawFragment" 10 | [source]: any 11 | 12 | __template: TemplateStringsArray 13 | __params: Array | IExecutableInsertQuery | IExecutableUpdateQuery | IExecutableDeleteQuery> 14 | 15 | constructor(template: TemplateStringsArray, params: Array | IExecutableInsertQuery | IExecutableUpdateQuery | IExecutableDeleteQuery>) { 16 | this.__template = template 17 | this.__params = params 18 | } 19 | __toSql(sqlBuilder: SqlBuilder, params: any[]): string { 20 | return sqlBuilder._rawFragment(params, this.__template, this.__params) 21 | } 22 | __toSqlForCondition(sqlBuilder: SqlBuilder, params: any[]): string { 23 | return this.__toSql(sqlBuilder, params) 24 | } 25 | 26 | __addWiths(sqlBuilder: HasIsValue, withs: Array>): void { 27 | const params = this.__params 28 | for (let i = 0, length = params.length; i < length; i++) { 29 | __addWiths(params[i], sqlBuilder, withs) 30 | } 31 | } 32 | __registerTableOrView(sqlBuilder: HasIsValue, requiredTablesOrViews: Set): void { 33 | const params = this.__params 34 | for (let i = 0, length = params.length; i < length; i++) { 35 | __registerTableOrView(params[i], sqlBuilder, requiredTablesOrViews) 36 | } 37 | } 38 | __registerRequiredColumn(sqlBuilder: HasIsValue, requiredColumns: Set, onlyForTablesOrViews: Set): void { 39 | const params = this.__params 40 | for (let i = 0, length = params.length; i < length; i++) { 41 | __registerRequiredColumn(params[i], sqlBuilder, requiredColumns, onlyForTablesOrViews) 42 | } 43 | } 44 | __getOldValues(sqlBuilder: HasIsValue): AnyTableOrView | undefined { 45 | const params = this.__params 46 | for (let i = 0, length = params.length; i < length; i++) { 47 | const result = __getOldValues(params[i], sqlBuilder) 48 | if (result) { 49 | return result 50 | } 51 | } 52 | return undefined 53 | } 54 | __getValuesForInsert(sqlBuilder: HasIsValue): AnyTableOrView | undefined { 55 | const params = this.__params 56 | for (let i = 0, length = params.length; i < length; i++) { 57 | const result = __getValuesForInsert(params[i], sqlBuilder) 58 | if (result) { 59 | return result 60 | } 61 | } 62 | return undefined 63 | } 64 | __isAllowed(sqlBuilder: HasIsValue): boolean { 65 | const params = this.__params 66 | for (let i = 0, length = params.length; i < length; i++) { 67 | const result = __isAllowed(params[i], sqlBuilder) 68 | if (!result) { 69 | return false 70 | } 71 | } 72 | return true 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/internal/WithViewImpl.ts: -------------------------------------------------------------------------------- 1 | import { HasIsValue, IWithView, __addWiths, __getTableOrViewPrivate, __ITableOrViewPrivate, __registerRequiredColumn, __registerTableOrView, AnyTableOrView } from "../utils/ITableOrView" 2 | import type { AliasedTableOrView, AsAliasedForUseInLeftJoin, AsForUseInLeftJoin } from "../utils/tableOrViewUtils" 3 | import type { SelectData, SqlBuilder, WithSelectData } from "../sqlBuilders/SqlBuilder" 4 | import { createColumnsFrom } from "./DBColumnImpl" 5 | import { isTableOrViewObject, source, type } from "../utils/symbols" 6 | import { __getValueSourcePrivate } from "../expressions/values" 7 | import type { RawFragment } from "../utils/RawFragment" 8 | import type { DBColumn } from "../utils/Column" 9 | import { __setColumnsForLeftJoin } from '../utils/leftJoinUtils' 10 | 11 | export class WithViewImpl implements IWithView, WithSelectData, __ITableOrViewPrivate { 12 | [isTableOrViewObject]: true = true; 13 | [source]: any 14 | [type]!: 'with' 15 | __sqlBuilder: SqlBuilder 16 | 17 | /* implements __ITableOrViewPrivate as private members*/ 18 | __name: string 19 | // @ts-ignore 20 | __as?: string 21 | // @ts-ignore 22 | __forUseInLeftJoin?: boolean 23 | // @ts-ignore 24 | __type: 'with' = 'with' 25 | __selectData: SelectData 26 | // @ts-ignore 27 | __originalWith?: WithViewImpl 28 | __ignoreWith?: boolean 29 | __recursive?: boolean 30 | // @ts-ignore 31 | __template?: RawFragment 32 | __hasExternalDependencies?: boolean 33 | 34 | constructor(sqlBuilder: SqlBuilder, name: string, selectData: SelectData) { 35 | this.__sqlBuilder = sqlBuilder 36 | this.__name = name 37 | this.__selectData = selectData 38 | if (selectData.__subSelectUsing) { 39 | this.__hasExternalDependencies = selectData.__subSelectUsing.length > 0 40 | } 41 | 42 | const columns = selectData.__columns 43 | createColumnsFrom(sqlBuilder, columns, this as any, this) 44 | } 45 | // Already defined in IWithView 46 | // [type]!: "with" 47 | // [tableOrViewRef]!: REF 48 | // [database]!: REF[typeof database] 49 | 50 | as(as: ALIAS): AliasedTableOrView { 51 | const result = new WithViewImpl(this.__sqlBuilder, this.__name, this.__selectData) 52 | result.__as = as 53 | result.__originalWith = this as any 54 | return result as any 55 | } 56 | forUseInLeftJoin(): AsForUseInLeftJoin { 57 | return this.forUseInLeftJoinAs('') as any 58 | } 59 | forUseInLeftJoinAs(as: ALIAS): AsAliasedForUseInLeftJoin { 60 | const result = new WithViewImpl(this.__sqlBuilder, this.__name, this.__selectData) 61 | result.__as = as 62 | result.__forUseInLeftJoin = true 63 | result.__originalWith = this as any 64 | __setColumnsForLeftJoin(result as any) 65 | return result as any 66 | } 67 | __addWiths(sqlBuilder: HasIsValue, withs: Array>): void { 68 | if (this.__ignoreWith) { 69 | return 70 | } 71 | 72 | if (this.__originalWith) { 73 | this.__originalWith.__addWiths(sqlBuilder, withs) 74 | } else if (!withs.includes(this as any)) { 75 | const withViews = this.__selectData.__withs 76 | for (let i = 0, length = withViews.length; i < length; i++) { 77 | const withView = withViews[i]! 78 | __getTableOrViewPrivate(withView).__addWiths(sqlBuilder, withs) 79 | } 80 | withs.push(this as any) 81 | } 82 | __addWiths(this.__template, sqlBuilder, withs) 83 | } 84 | __registerTableOrView(sqlBuilder: HasIsValue, requiredTablesOrViews: Set): void { 85 | requiredTablesOrViews.add(this) 86 | __registerTableOrView(this.__template, sqlBuilder, requiredTablesOrViews) 87 | } 88 | __registerRequiredColumn(sqlBuilder: HasIsValue, requiredColumns: Set, onlyForTablesOrViews: Set): void { 89 | __registerRequiredColumn(this.__template, sqlBuilder, requiredColumns, onlyForTablesOrViews) 90 | } 91 | __getOldValues(_sqlBuilder: HasIsValue): AnyTableOrView | undefined { 92 | return undefined 93 | } 94 | __getValuesForInsert(_sqlBuilder: HasIsValue): AnyTableOrView | undefined { 95 | return undefined 96 | } 97 | __isAllowed(_sqlBuilder: HasIsValue): boolean { 98 | return true 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/queryBuilders/SequenceQueryBuilder.ts: -------------------------------------------------------------------------------- 1 | import type { Sequence } from "../expressions/sequence" 2 | import type { TypeAdapter } from "../TypeAdapter" 3 | import { SequenceValueSource } from "../internal/ValueSourceImpl" 4 | import { ValueType } from "../expressions/values" 5 | 6 | export class SequenceQueryBuilder implements Sequence { 7 | __sequenceName: string 8 | __columnType: ValueType 9 | __columnTypeName: string 10 | __typeAdapter: TypeAdapter | undefined 11 | 12 | constructor(sequenceName: string, columnType: ValueType, columnTypeName: string, typeAdapter: TypeAdapter | undefined) { 13 | this.__sequenceName = sequenceName 14 | this.__columnType = columnType 15 | this.__columnTypeName = columnTypeName 16 | this.__typeAdapter = typeAdapter 17 | } 18 | nextValue(): any { 19 | return new SequenceValueSource('_nextSequenceValue', this.__sequenceName, this.__columnType, this.__columnTypeName, 'required', this.__typeAdapter) as any 20 | } 21 | currentValue(): any { 22 | return new SequenceValueSource('_currentSequenceValue', this.__sequenceName, this.__columnType, this.__columnTypeName, 'required', this.__typeAdapter) as any 23 | } 24 | } -------------------------------------------------------------------------------- /src/queryRunners/BetterSqlite3QueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { DatabaseType, PromiseProvider } from "./QueryRunner" 2 | import type { Database } from 'better-sqlite3' 3 | import { SqlTransactionQueryRunner } from "./SqlTransactionQueryRunner" 4 | 5 | export interface BetterSqlite3QueryRunnerConfig { 6 | promise?: PromiseProvider 7 | } 8 | 9 | export class BetterSqlite3QueryRunner extends SqlTransactionQueryRunner { 10 | readonly database: DatabaseType 11 | readonly connection: Database 12 | readonly promise: PromiseProvider 13 | 14 | constructor(connection: Database, config?: BetterSqlite3QueryRunnerConfig) { 15 | super() 16 | this.connection = connection 17 | this.database = 'sqlite' 18 | this.promise = config?.promise || Promise 19 | } 20 | 21 | useDatabase(database: DatabaseType): void { 22 | if (database !== 'sqlite') { 23 | throw new Error('Unsupported database: ' + database + '. BetterSqlite3QueryRunner only supports sqlite databases') 24 | } 25 | } 26 | 27 | getNativeRunner(): Database { 28 | return this.connection 29 | } 30 | 31 | getCurrentNativeTransaction(): undefined { 32 | return undefined 33 | } 34 | 35 | execute(fn: (connection: unknown, transaction?: unknown) => Promise): Promise { 36 | return fn(this.connection) 37 | } 38 | 39 | protected executeQueryReturning(query: string, params: any[]): Promise { 40 | try { 41 | const rows = this.connection.prepare(query).safeIntegers(true).all(params) 42 | return this.promise.resolve(rows) 43 | } catch (e) { 44 | return this.promise.reject(e) 45 | } 46 | } 47 | protected executeMutation(query: string, params: any[]): Promise { 48 | try { 49 | return this.promise.resolve(this.connection.prepare(query).run(params).changes) 50 | } catch (e) { 51 | return this.promise.reject(e) 52 | } 53 | } 54 | executeInsertReturningLastInsertedId(query: string, params: any[] = []): Promise { 55 | if (this.containsInsertReturningClause(query, params)) { 56 | return super.executeInsertReturningLastInsertedId(query, params) 57 | } 58 | 59 | try { 60 | return this.promise.resolve(this.connection.prepare(query).safeIntegers(true).run(params).lastInsertRowid) 61 | } catch (e) { 62 | return this.promise.reject(e) 63 | } 64 | } 65 | addParam(params: any[], value: any): string { 66 | if (typeof value === 'boolean') { 67 | params.push(Number(value)) 68 | } else { 69 | params.push(value) 70 | } 71 | return '?' 72 | } 73 | createResolvedPromise(result: RESULT): Promise { 74 | return this.promise.resolve(result) 75 | } 76 | createRejectedPromise(error: any): Promise { 77 | return this.promise.reject(error) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/queryRunners/ConsoleLogNoopQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import { ConsoleLogQueryRunner } from "./ConsoleLogQueryRunner" 2 | import { NoopQueryRunner } from "./NoopQueryRunner" 3 | import type { DatabaseType, PromiseProvider } from "./QueryRunner" 4 | 5 | export interface ConsoleLogNoopQueryRunnerConfig { 6 | database?: DatabaseType 7 | promise?: PromiseProvider 8 | } 9 | 10 | export class ConsoleLogNoopQueryRunner extends ConsoleLogQueryRunner { 11 | 12 | constructor(databaseOrConfig: DatabaseType | ConsoleLogNoopQueryRunnerConfig = 'noopDB') { 13 | super(new NoopQueryRunner(databaseOrConfig)) 14 | } 15 | } -------------------------------------------------------------------------------- /src/queryRunners/ConsoleLogQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { QueryRunner } from "./QueryRunner" 2 | import { LoggingQueryRunner } from "./LoggingQueryRunner" 3 | 4 | export type TimeGranularity = 'ms' | 'us' | 'ns' 5 | 6 | export interface ConsoleLogQueryRunnerOpts { 7 | timeGranularity?: TimeGranularity 8 | logTimestamps?: boolean 9 | logDurations?: boolean 10 | logResults?: boolean 11 | paramsAsObject?: boolean 12 | includeLogPhase?: boolean 13 | } 14 | 15 | export class ConsoleLogQueryRunner extends LoggingQueryRunner { 16 | constructor(queryRunner: T, opts?: ConsoleLogQueryRunnerOpts) { 17 | const logEnd = opts?.logResults || opts?.logDurations || opts?.logTimestamps 18 | const timeGranularity = opts?.timeGranularity ?? 'ms' 19 | super({ 20 | onQuery: (queryType, query, params, timestamps) => { 21 | const phase = opts?.includeLogPhase ? ' (onQuery)' : '' 22 | const ts = opts?.logTimestamps 23 | ? `[${formatDuration(timestamps.startedAt, timeGranularity)}] ` 24 | : '' 25 | const separator = opts?.logTimestamps ? ' ' : '' 26 | if (opts?.paramsAsObject) { 27 | console.log(`${ts}${separator}${queryType}${phase}`, { query, params }) 28 | } else { 29 | console.log(`${ts}${separator}${queryType}${phase}`, query, params) 30 | } 31 | }, 32 | onQueryResult: logEnd ? (queryType, query, params, result, timestamps) => { 33 | const phase = opts?.includeLogPhase ? ' (onQueryResult)' : '' 34 | const ts = opts?.logTimestamps 35 | ? `[${formatDuration(timestamps.endedAt, timeGranularity)}]` 36 | : '' 37 | const duration = opts?.logDurations 38 | ? `[Took ${ 39 | formatDuration( timestamps.endedAt - timestamps.startedAt, timeGranularity) 40 | }${timeGranularity}]` 41 | : '' 42 | const separator = opts?.logTimestamps || opts?.logDurations ? ' ' : '' 43 | if ( opts?.logResults) { 44 | if (opts?.paramsAsObject) { 45 | console.log(`${ts}${duration}${separator}${queryType}${phase}`, { query, params }) 46 | } else { 47 | console.log(`${ts}${duration}${separator}${queryType}${phase}`, query, params) 48 | } 49 | } else { 50 | if (opts?.paramsAsObject) { 51 | console.log(`${ts}${duration}${separator}${queryType}${phase}`, { query, params, result }) 52 | } else { 53 | console.log(`${ts}${duration}${separator}${queryType}${phase}`, query, params, result) 54 | } 55 | } 56 | } : undefined, 57 | onQueryError: logEnd ? (queryType, query, params, error, timestamps) => { 58 | const phase = opts?.includeLogPhase ? ' (onQueryError)' : '' 59 | const ts = opts?.logTimestamps 60 | ? `[${formatDuration(timestamps.endedAt, timeGranularity)}]` 61 | : '' 62 | const duration = opts?.logDurations 63 | ? `[Took ${ 64 | formatDuration( timestamps.endedAt - timestamps.startedAt, timeGranularity) 65 | }${timeGranularity}]` 66 | : '' 67 | const separator = opts?.logTimestamps || opts?.logDurations ? ' ' : '' 68 | if (opts?.paramsAsObject) { 69 | console.log(`${ts}${duration}${separator}${queryType}${phase}`, { query, params, error }) 70 | } else { 71 | console.log(`${ts}${duration}${separator}${queryType}${phase}`, query, params, error) 72 | } 73 | } : undefined 74 | }, queryRunner) 75 | } 76 | } 77 | 78 | const formatDuration = (durationNS: bigint, granularity: TimeGranularity) => { 79 | switch (granularity) { 80 | case 'ns': return durationNS 81 | case 'us': return durationNS/1000n 82 | case 'ms': return durationNS/1000000n 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/queryRunners/ManagedTransactionPoolQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import { AbstractPoolQueryRunner } from "./AbstractPoolQueryRunner"; 2 | import type { BeginTransactionOpts, QueryRunner } from "./QueryRunner"; 3 | 4 | export abstract class ManagedTransactionPoolQueryRunner extends AbstractPoolQueryRunner { 5 | executeInTransaction(fn: () => Promise, outermostQueryRunner: QueryRunner, opts: BeginTransactionOpts = []): Promise { 6 | return outermostQueryRunner.executeBeginTransaction(opts).then(() => { 7 | let result = fn() 8 | return result.then((r) => { 9 | return outermostQueryRunner.executeCommit(opts as any).then(() => { 10 | return r 11 | }) 12 | }).catch((e) => { 13 | return outermostQueryRunner.executeRollback(opts as any).then(() => { 14 | throw e 15 | }, () => { 16 | // Throw the innermost error 17 | throw e 18 | }) 19 | }) 20 | }) 21 | } 22 | } -------------------------------------------------------------------------------- /src/queryRunners/ManagedTransactionQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import { AbstractQueryRunner } from "./AbstractQueryRunner" 2 | import type { BeginTransactionOpts, QueryRunner } from "./QueryRunner" 3 | 4 | export abstract class ManagedTransactionQueryRunner extends AbstractQueryRunner { 5 | executeInTransaction(fn: () => Promise, outermostQueryRunner: QueryRunner, opts: BeginTransactionOpts = []): Promise { 6 | return outermostQueryRunner.executeBeginTransaction(opts).then(() => { 7 | let result = fn() 8 | return result.then((r) => { 9 | return outermostQueryRunner.executeCommit(opts as any).then(() => { 10 | return r 11 | }) 12 | }).catch((e) => { 13 | return outermostQueryRunner.executeRollback(opts as any).then(() => { 14 | throw e 15 | }, () => { 16 | // Throw the innermost error 17 | throw e 18 | }) 19 | }) 20 | }) 21 | } 22 | } -------------------------------------------------------------------------------- /src/queryRunners/MariaDBPoolQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { DatabaseType, QueryRunner } from "./QueryRunner" 2 | import type { Pool, PoolConnection } from 'mariadb' 3 | import { MariaDBQueryRunner } from "./MariaDBQueryRunner" 4 | import { ManagedTransactionPoolQueryRunner } from "./ManagedTransactionPoolQueryRunner" 5 | 6 | export class MariaDBPoolQueryRunner extends ManagedTransactionPoolQueryRunner { 7 | readonly database: DatabaseType 8 | readonly pool: Pool 9 | 10 | constructor(pool: Pool, database: 'mariaDB' | 'mySql' = 'mariaDB') { 11 | super() 12 | this.pool = pool 13 | this.database = database 14 | } 15 | 16 | useDatabase(database: DatabaseType): void { 17 | if (database !== 'mariaDB' && database !== 'mySql') { 18 | throw new Error('Unsupported database: ' + database + '. MariaDBQueryRunner only supports mariaDB or mySql databases') 19 | } else { 20 | // @ts-ignore 21 | this.database = database 22 | } 23 | } 24 | getNativeRunner(): unknown { 25 | return this.pool 26 | } 27 | addParam(params: any[], value: any): string { 28 | params.push(value) 29 | return '?' 30 | } 31 | protected createQueryRunner(): Promise { 32 | return this.pool.getConnection().then(mariaDBConnection => new MariaDBQueryRunner(mariaDBConnection, this.database as any)) 33 | } 34 | protected releaseQueryRunner(queryRunner: QueryRunner): void { 35 | (queryRunner.getNativeRunner() as PoolConnection).release() 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/queryRunners/MariaDBQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { BeginTransactionOpts, CommitOpts, DatabaseType, RollbackOpts } from "./QueryRunner" 2 | import type { Connection, UpsertResult } from 'mariadb' 3 | import { DelegatedSetTransactionQueryRunner } from "./DelegatedSetTransactionQueryRunner" 4 | 5 | export class MariaDBQueryRunner extends DelegatedSetTransactionQueryRunner { 6 | readonly database: DatabaseType 7 | readonly connection: Connection 8 | 9 | constructor(connection: Connection, database: 'mariaDB' | 'mySql' = 'mariaDB') { 10 | super() 11 | this.connection = connection 12 | this.database = database 13 | } 14 | 15 | useDatabase(database: DatabaseType): void { 16 | if (database !== 'mariaDB' && database !== 'mySql') { 17 | throw new Error('Unsupported database: ' + database + '. MariaDBQueryRunner only supports mariaDB or mySql databases') 18 | } else { 19 | // @ts-ignore 20 | this.database = database 21 | } 22 | } 23 | 24 | getNativeRunner(): Connection { 25 | return this.connection 26 | } 27 | 28 | getCurrentNativeTransaction(): undefined { 29 | return undefined 30 | } 31 | 32 | execute(fn: (connection: unknown, transaction?: unknown) => Promise): Promise { 33 | return fn(this.connection) 34 | } 35 | 36 | protected executeQueryReturning(query: string, params: any[]): Promise { 37 | return this.connection.query({ sql: query, bigNumberStrings: true }, params) 38 | } 39 | protected executeMutation(query: string, params: any[]): Promise { 40 | return this.connection.query({ sql: query, bigNumberStrings: true }, params).then((result: UpsertResult) => result.affectedRows) 41 | } 42 | executeInsertReturningLastInsertedId(query: string, params: any[] = []): Promise { 43 | if (this.containsInsertReturningClause(query, params)) { 44 | return super.executeInsertReturningLastInsertedId(query, params) 45 | } 46 | 47 | return this.connection.query({ sql: query, bigNumberStrings: true }, params).then((result: UpsertResult) => result.insertId) 48 | } 49 | doBeginTransaction(_opts: BeginTransactionOpts): Promise { 50 | return this.connection.beginTransaction() 51 | } 52 | doCommit(_opts: CommitOpts): Promise { 53 | return this.connection.commit() 54 | } 55 | doRollback(_opts: RollbackOpts): Promise { 56 | return this.connection.rollback() 57 | } 58 | addParam(params: any[], value: any): string { 59 | params.push(value) 60 | return '?' 61 | } 62 | } -------------------------------------------------------------------------------- /src/queryRunners/MssqlPoolPromiseQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { DatabaseType, QueryRunner } from "./QueryRunner" 2 | import type { ConnectionPool, Transaction } from 'mssql' 3 | import { MssqlPoolQueryRunner } from "./MssqlPoolQueryRunner" 4 | import { ManagedTransactionPoolQueryRunner } from "./ManagedTransactionPoolQueryRunner" 5 | 6 | export class MssqlPoolPromiseQueryRunner extends ManagedTransactionPoolQueryRunner { 7 | readonly database: DatabaseType 8 | readonly promisePool: Promise 9 | 10 | constructor(promisePool: Promise) { 11 | super() 12 | this.promisePool = promisePool 13 | this.database = 'sqlServer' 14 | } 15 | 16 | useDatabase(database: DatabaseType): void { 17 | if (database !== 'sqlServer') { 18 | throw new Error('Unsupported database: ' + database + '. MssqlPoolPromiseQueryRunner only supports sqlServer databases') 19 | } 20 | } 21 | getNativeRunner(): Promise { 22 | return this.promisePool 23 | } 24 | getCurrentNativeTransaction(): Transaction | undefined { 25 | return super.getCurrentNativeTransaction() as any 26 | } 27 | addParam(params: any[], value: any): string { 28 | const index = params.length 29 | params.push(value) 30 | return '@' + index 31 | } 32 | protected createQueryRunner(): Promise { 33 | return this.promisePool.then(pool => new MssqlPoolQueryRunner(pool)) 34 | } 35 | protected releaseQueryRunner(_queryRunner: QueryRunner): void { 36 | // Do nothing 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/queryRunners/MySql2PoolQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { DatabaseType, QueryRunner } from "./QueryRunner" 2 | import type { Pool, PoolConnection } from "mysql2" 3 | import { MySql2QueryRunner } from "./MySql2QueryRunner" 4 | import { ManagedTransactionPoolQueryRunner } from "./ManagedTransactionPoolQueryRunner" 5 | 6 | export class MySql2PoolQueryRunner extends ManagedTransactionPoolQueryRunner { 7 | readonly database: DatabaseType 8 | readonly pool: Pool 9 | 10 | constructor(pool: Pool, database: 'mariaDB' | 'mySql' = 'mySql') { 11 | super() 12 | this.pool = pool 13 | this.database = database 14 | } 15 | 16 | useDatabase(database: DatabaseType): void { 17 | if (database !== 'mariaDB' && database !== 'mySql') { 18 | throw new Error('Unsupported database: ' + database + '. MySql2PoolQueryRunner only supports mySql or mariaDB databases') 19 | } else { 20 | // @ts-ignore 21 | this.database = database 22 | } 23 | } 24 | getNativeRunner(): unknown { 25 | return this.pool 26 | } 27 | addParam(params: any[], value: any): string { 28 | params.push(value) 29 | return '?' 30 | } 31 | protected createQueryRunner(): Promise { 32 | return new Promise((resolve, reject) => { 33 | this.pool.getConnection((error, mysql2Connection) => { 34 | if (error) { 35 | reject(error) 36 | } else { 37 | resolve(new MySql2QueryRunner(mysql2Connection, this.database as any)) 38 | } 39 | }) 40 | }) 41 | } 42 | protected releaseQueryRunner(queryRunner: QueryRunner): void { 43 | (queryRunner.getNativeRunner() as PoolConnection).release() 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/queryRunners/MySql2QueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { BeginTransactionOpts, CommitOpts, DatabaseType, RollbackOpts } from "./QueryRunner" 2 | import type { Connection, QueryError, ResultSetHeader, RowDataPacket } from "mysql2" 3 | import { DelegatedSetTransactionQueryRunner } from "./DelegatedSetTransactionQueryRunner" 4 | 5 | export class MySql2QueryRunner extends DelegatedSetTransactionQueryRunner { 6 | readonly database: DatabaseType 7 | readonly connection: Connection 8 | 9 | constructor(connection: Connection, database: 'mariaDB' | 'mySql' = 'mySql') { 10 | super() 11 | this.connection = connection 12 | this.database = database 13 | } 14 | 15 | useDatabase(database: DatabaseType): void { 16 | if (database !== 'mariaDB' && database !== 'mySql') { 17 | throw new Error('Unsupported database: ' + database + '. MySql2QueryRunner only supports mySql or mariaDB databases') 18 | } else { 19 | // @ts-ignore 20 | this.database = database 21 | } 22 | } 23 | 24 | getNativeRunner(): Connection { 25 | return this.connection 26 | } 27 | 28 | getCurrentNativeTransaction(): undefined { 29 | return undefined 30 | } 31 | 32 | execute(fn: (connection: unknown, transaction?: unknown) => Promise): Promise { 33 | return fn(this.connection) 34 | } 35 | 36 | protected executeQueryReturning(query: string, params: any[]): Promise { 37 | return new Promise((resolve, reject) => { 38 | this.connection.query(query, params, (error: QueryError | null, results: RowDataPacket[]) => { 39 | if (error) { 40 | reject(error) 41 | } else { 42 | resolve(results) 43 | } 44 | }) 45 | }) 46 | } 47 | protected executeMutation(query: string, params: any[]): Promise { 48 | return new Promise((resolve, reject) => { 49 | this.connection.query(query, params, (error: QueryError | null, results: ResultSetHeader) => { 50 | if (error) { 51 | reject(error) 52 | } else { 53 | resolve(results.affectedRows) 54 | } 55 | }) 56 | }) 57 | } 58 | executeInsertReturningLastInsertedId(query: string, params: any[] = []): Promise { 59 | if (this.containsInsertReturningClause(query, params)) { 60 | return super.executeInsertReturningLastInsertedId(query, params) 61 | } 62 | 63 | return new Promise((resolve, reject) => { 64 | this.connection.query(query, params, (error: QueryError | null, results: ResultSetHeader) => { 65 | if (error) { 66 | reject(error) 67 | } else { 68 | resolve(results.insertId) 69 | } 70 | }) 71 | }) 72 | } 73 | doBeginTransaction(_opts: BeginTransactionOpts): Promise { 74 | return new Promise((resolve, reject) => { 75 | this.connection.beginTransaction((error: QueryError | null) => { 76 | if (error) { 77 | reject(error) 78 | } else { 79 | resolve(undefined) 80 | } 81 | }) 82 | }) 83 | } 84 | doCommit(_opts: CommitOpts): Promise { 85 | return new Promise((resolve, reject) => { 86 | this.connection.commit((error: QueryError | null) => { 87 | if (error) { 88 | reject(error) 89 | } else { 90 | resolve(undefined) 91 | } 92 | }) 93 | }) 94 | } 95 | doRollback(_opts: RollbackOpts): Promise { 96 | return new Promise((resolve, reject) => { 97 | this.connection.beginTransaction((error: QueryError | null) => { 98 | if (error) { 99 | reject(error) 100 | } else { 101 | resolve(undefined) 102 | } 103 | }) 104 | }) 105 | } 106 | addParam(params: any[], value: any): string { 107 | params.push(value) 108 | return '?' 109 | } 110 | } -------------------------------------------------------------------------------- /src/queryRunners/MySqlPoolQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { DatabaseType, QueryRunner } from "./QueryRunner" 2 | import type { Pool, PoolConnection } from "mysql" 3 | import { MySqlQueryRunner } from "./MySqlQueryRunner" 4 | import { ManagedTransactionPoolQueryRunner } from "./ManagedTransactionPoolQueryRunner" 5 | 6 | export class MySqlPoolQueryRunner extends ManagedTransactionPoolQueryRunner { 7 | readonly database: DatabaseType 8 | readonly pool: Pool 9 | 10 | constructor(pool: Pool, database: 'mariaDB' | 'mySql' = 'mySql') { 11 | super() 12 | this.pool = pool 13 | this.database = database 14 | } 15 | 16 | useDatabase(database: DatabaseType): void { 17 | if (database !== 'mariaDB' && database !== 'mySql') { 18 | throw new Error('Unsupported database: ' + database + '. MySqlPoolQueryRunner only supports mySql or mariaDB databases') 19 | } else { 20 | // @ts-ignore 21 | this.database = database 22 | } 23 | } 24 | getNativeRunner(): Pool { 25 | return this.pool 26 | } 27 | addParam(params: any[], value: any): string { 28 | params.push(value) 29 | return '?' 30 | } 31 | protected createQueryRunner(): Promise { 32 | return new Promise((resolve, reject) => { 33 | this.pool.getConnection((error, mysqlConnection) => { 34 | if (error) { 35 | reject(error) 36 | } else { 37 | resolve(new MySqlQueryRunner(mysqlConnection, this.database as any)) 38 | } 39 | }) 40 | }) 41 | } 42 | protected releaseQueryRunner(queryRunner: QueryRunner): void { 43 | (queryRunner.getNativeRunner() as PoolConnection).release() 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/queryRunners/MySqlQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { BeginTransactionOpts, CommitOpts, DatabaseType, RollbackOpts } from "./QueryRunner" 2 | import type { Connection } from "mysql" 3 | import { DelegatedSetTransactionQueryRunner } from "./DelegatedSetTransactionQueryRunner" 4 | 5 | export class MySqlQueryRunner extends DelegatedSetTransactionQueryRunner { 6 | readonly database: DatabaseType 7 | readonly connection: Connection 8 | 9 | constructor(connection: Connection, database: 'mariaDB' | 'mySql' = 'mySql') { 10 | super() 11 | this.connection = connection 12 | this.database = database 13 | } 14 | 15 | useDatabase(database: DatabaseType): void { 16 | if (database !== 'mariaDB' && database !== 'mySql') { 17 | throw new Error('Unsupported database: ' + database + '. MySqlQueryRunner only supports mySql or mariaDB databases') 18 | } else { 19 | // @ts-ignore 20 | this.database = database 21 | } 22 | } 23 | 24 | getNativeRunner(): Connection { 25 | return this.connection 26 | } 27 | 28 | getCurrentNativeTransaction(): undefined { 29 | return undefined 30 | } 31 | 32 | execute(fn: (connection: unknown, transaction?: unknown) => Promise): Promise { 33 | return fn(this.connection) 34 | } 35 | 36 | protected executeQueryReturning(query: string, params: any[]): Promise { 37 | return new Promise((resolve, reject) => { 38 | this.connection.query(query, params, (error, results) => { 39 | if (error) { 40 | reject(error) 41 | } else { 42 | resolve(results) 43 | } 44 | }) 45 | }) 46 | } 47 | protected executeMutation(query: string, params: any[]): Promise { 48 | return new Promise((resolve, reject) => { 49 | this.connection.query(query, params, (error, results) => { 50 | if (error) { 51 | reject(error) 52 | } else { 53 | resolve(results.affectedRows) 54 | } 55 | }) 56 | }) 57 | } 58 | executeInsertReturningLastInsertedId(query: string, params: any[] = []): Promise { 59 | if (this.containsInsertReturningClause(query, params)) { 60 | return super.executeInsertReturningLastInsertedId(query, params) 61 | } 62 | 63 | return new Promise((resolve, reject) => { 64 | this.connection.query(query, params, (error, results) => { 65 | if (error) { 66 | reject(error) 67 | } else { 68 | resolve(results.insertId) 69 | } 70 | }) 71 | }) 72 | } 73 | doBeginTransaction(_opts: BeginTransactionOpts): Promise { 74 | return new Promise((resolve, reject) => { 75 | this.connection.beginTransaction((error) => { 76 | if (error) { 77 | reject(error) 78 | } else { 79 | resolve(undefined) 80 | } 81 | }) 82 | }) 83 | } 84 | doCommit(_opts: CommitOpts): Promise { 85 | return new Promise((resolve, reject) => { 86 | this.connection.commit((error) => { 87 | if (error) { 88 | reject(error) 89 | } else { 90 | resolve(undefined) 91 | } 92 | }) 93 | }) 94 | } 95 | doRollback(_opts: RollbackOpts): Promise { 96 | return new Promise((resolve, reject) => { 97 | this.connection.beginTransaction((error) => { 98 | if (error) { 99 | reject(error) 100 | } else { 101 | resolve(undefined) 102 | } 103 | }) 104 | }) 105 | } 106 | addParam(params: any[], value: any): string { 107 | params.push(value) 108 | return '?' 109 | } 110 | } -------------------------------------------------------------------------------- /src/queryRunners/OracleDBPoolPromiseQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { DatabaseType, QueryRunner } from "./QueryRunner" 2 | import type { Pool, Connection } from 'oracledb' 3 | import { BIND_OUT } from 'oracledb' 4 | import { OracleDBQueryRunner } from "./OracleDBQueryRunner" 5 | import { ManagedTransactionPoolQueryRunner } from "./ManagedTransactionPoolQueryRunner" 6 | 7 | export class OracleDBPoolPromiseQueryRunner extends ManagedTransactionPoolQueryRunner { 8 | readonly database: DatabaseType 9 | readonly promisePool: Promise 10 | 11 | constructor(promisePool: Promise) { 12 | super() 13 | this.promisePool = promisePool 14 | this.database = 'oracle' 15 | } 16 | 17 | useDatabase(database: DatabaseType): void { 18 | if (database !== 'oracle') { 19 | throw new Error('Unsupported database: ' + database + '. OracleDBPoolPromiseQueryRunner only supports oracle databases') 20 | } 21 | } 22 | getNativeRunner(): Promise { 23 | return this.promisePool 24 | } 25 | addParam(params: any[], value: any): string { 26 | const index = params.length 27 | params.push(value) 28 | return ':' + index 29 | } 30 | addOutParam(params: any[], name: string): string { 31 | const index = params.length 32 | if (name) { 33 | params.push({dir: BIND_OUT, as: name}) 34 | } else { 35 | params.push({dir: BIND_OUT}) 36 | } 37 | return ':' + index 38 | } 39 | protected createQueryRunner(): Promise { 40 | return this.promisePool.then(pool => pool.getConnection()).then(connection => new OracleDBQueryRunner(connection)) 41 | } 42 | protected releaseQueryRunner(queryRunner: QueryRunner): void { 43 | (queryRunner.getNativeRunner() as Connection).close() 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/queryRunners/OracleDBPoolQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { DatabaseType, QueryRunner } from "./QueryRunner" 2 | import type { Pool, Connection } from 'oracledb' 3 | import { BIND_OUT } from 'oracledb' 4 | import { OracleDBQueryRunner } from "./OracleDBQueryRunner" 5 | import { ManagedTransactionPoolQueryRunner } from "./ManagedTransactionPoolQueryRunner" 6 | 7 | export class OracleDBPoolQueryRunner extends ManagedTransactionPoolQueryRunner { 8 | readonly database: DatabaseType 9 | readonly pool: Pool 10 | 11 | constructor(pool: Pool) { 12 | super() 13 | this.pool = pool 14 | this.database = 'oracle' 15 | } 16 | 17 | useDatabase(database: DatabaseType): void { 18 | if (database !== 'oracle') { 19 | throw new Error('Unsupported database: ' + database + '. OracleDBPoolQueryRunner only supports oracle databases') 20 | } 21 | } 22 | getNativeRunner(): Pool { 23 | return this.pool 24 | } 25 | addParam(params: any[], value: any): string { 26 | const index = params.length 27 | params.push(value) 28 | return ':' + index 29 | } 30 | addOutParam(params: any[], name: string): string { 31 | const index = params.length 32 | if (name) { 33 | params.push({dir: BIND_OUT, as: name}) 34 | } else { 35 | params.push({dir: BIND_OUT}) 36 | } 37 | return ':' + index 38 | } 39 | protected createQueryRunner(): Promise { 40 | return this.pool.getConnection().then(connection => new OracleDBQueryRunner(connection)) 41 | } 42 | protected releaseQueryRunner(queryRunner: QueryRunner): void { 43 | (queryRunner.getNativeRunner() as Connection).close() 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/queryRunners/PgPoolQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { DatabaseType, QueryRunner } from "./QueryRunner" 2 | import type { Pool, PoolClient } from 'pg' 3 | import { PgQueryRunner } from "./PgQueryRunner" 4 | import { ManagedTransactionPoolQueryRunner } from "./ManagedTransactionPoolQueryRunner" 5 | 6 | export interface PgPoolQueryRunnerConfig { 7 | allowNestedTransactions?: boolean 8 | } 9 | 10 | export class PgPoolQueryRunner extends ManagedTransactionPoolQueryRunner { 11 | readonly database: DatabaseType 12 | readonly pool: Pool 13 | private config?: PgPoolQueryRunnerConfig 14 | 15 | constructor(pool: Pool, config?: PgPoolQueryRunnerConfig) { 16 | super() 17 | this.pool = pool 18 | this.database = 'postgreSql' 19 | this.config = config 20 | } 21 | 22 | useDatabase(database: DatabaseType): void { 23 | if (database !== 'postgreSql') { 24 | throw new Error('Unsupported database: ' + database + '. PgPoolQueryRunner only supports postgreSql databases') 25 | } 26 | } 27 | getNativeRunner(): Pool { 28 | return this.pool 29 | } 30 | addParam(params: any[], value: any): string { 31 | params.push(value) 32 | return '$' + params.length 33 | } 34 | protected createQueryRunner(): Promise { 35 | return this.pool.connect().then(connection => new PgQueryRunner(connection, this.config)) 36 | } 37 | protected releaseQueryRunner(queryRunner: QueryRunner): void { 38 | (queryRunner.getNativeRunner() as PoolClient).release() 39 | } 40 | nestedTransactionsSupported(): boolean { 41 | return !!this.config?.allowNestedTransactions 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/queryRunners/PgQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { DatabaseType } from "./QueryRunner" 2 | import type { ClientBase } from 'pg' 3 | import { SqlTransactionQueryRunner } from "./SqlTransactionQueryRunner" 4 | 5 | export interface PgQueryRunnerConfig { 6 | allowNestedTransactions?: boolean 7 | } 8 | 9 | export class PgQueryRunner extends SqlTransactionQueryRunner { 10 | readonly database: DatabaseType 11 | readonly connection: ClientBase 12 | private config?: PgQueryRunnerConfig 13 | 14 | constructor(connection: ClientBase, config?: PgQueryRunnerConfig) { 15 | super() 16 | this.connection = connection 17 | this.database = 'postgreSql' 18 | this.config = config 19 | } 20 | 21 | useDatabase(database: DatabaseType): void { 22 | if (database !== 'postgreSql') { 23 | throw new Error('Unsupported database: ' + database + '. PgQueryRunner only supports postgreSql databases') 24 | } 25 | } 26 | 27 | getNativeRunner(): ClientBase { 28 | return this.connection 29 | } 30 | 31 | getCurrentNativeTransaction(): undefined { 32 | return undefined 33 | } 34 | 35 | execute(fn: (connection: unknown, transaction?: unknown) => Promise): Promise { 36 | return fn(this.connection) 37 | } 38 | 39 | protected executeQueryReturning(query: string, params: any[]): Promise { 40 | return this.connection.query(query, params).then((result) => result.rows) 41 | } 42 | protected executeMutation(query: string, params: any[]): Promise { 43 | return this.connection.query(query, params).then((result) => result.rowCount || 0) 44 | } 45 | addParam(params: any[], value: any): string { 46 | params.push(value) 47 | return '$' + params.length 48 | } 49 | nestedTransactionsSupported(): boolean { 50 | return !!this.config?.allowNestedTransactions 51 | } 52 | } -------------------------------------------------------------------------------- /src/queryRunners/SqlTransactionQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import { ManagedTransactionQueryRunner } from "./ManagedTransactionQueryRunner" 2 | import { BeginTransactionOpts, CommitOpts, RollbackOpts } from "./QueryRunner" 3 | 4 | export abstract class SqlTransactionQueryRunner extends ManagedTransactionQueryRunner { 5 | private transactionLevel = 0 6 | executeBeginTransaction(opts: BeginTransactionOpts = []): Promise { 7 | const transactionLevel = this.transactionLevel 8 | if (!this.nestedTransactionsSupported() && transactionLevel >= 1) { 9 | return this.createRejectedPromise(new Error(this.database + " doesn't support nested transactions (using " + this.constructor.name + ")")) 10 | } 11 | 12 | let sql 13 | try { 14 | sql = this.createBeginTransactionQuery(opts) 15 | } catch (error) { 16 | return this.createRejectedPromise(error) 17 | } 18 | return this.executeMutation(sql, []).then(() => { 19 | this.transactionLevel++ 20 | if (this.transactionLevel !== transactionLevel + 1) { 21 | throw new Error('Forbidden concurrent usage of the query runner was detected when it tried to start a transaction.') 22 | } 23 | return undefined 24 | }) 25 | } 26 | executeCommit(_opts: CommitOpts = []): Promise { 27 | if (this.transactionLevel <= 0) { 28 | return Promise.reject(new Error('Not in an transaction, you cannot commit the transaction')) 29 | } 30 | 31 | return this.executeMutation('commit', []).then(() => { 32 | // Transaction count only modified when commit successful, in case of error there is still an open transaction 33 | this.transactionLevel-- 34 | if (this.transactionLevel < 0) { 35 | this.transactionLevel = 0 36 | } 37 | return undefined 38 | }) 39 | } 40 | executeRollback(_opts: RollbackOpts = []): Promise { 41 | if (this.transactionLevel <= 0) { 42 | return Promise.reject(new Error('Not in an transaction, you cannot rollback the transaction')) 43 | } 44 | 45 | return this.executeMutation('rollback', []).then(() => { 46 | this.transactionLevel-- 47 | if (this.transactionLevel < 0) { 48 | this.transactionLevel = 0 49 | } 50 | return undefined 51 | }, (error) => { 52 | this.transactionLevel-- 53 | if (this.transactionLevel < 0) { 54 | this.transactionLevel = 0 55 | } 56 | throw error 57 | }) 58 | } 59 | isTransactionActive(): boolean { 60 | return this.transactionLevel > 0 61 | } 62 | getTransactionLevel(opts: BeginTransactionOpts): string | undefined { 63 | const level = opts[0] 64 | if (this.database === 'sqlite' && level) { 65 | throw new Error(this.database + " doesn't support the transactions level: " + level) 66 | } 67 | if (!level || level === 'read uncommitted' || level === 'read committed' || level === 'repeatable read' || level === 'serializable') { 68 | return level 69 | } 70 | throw new Error(this.database + " doesn't support the transactions level: " + level) 71 | } 72 | getTransactionAccessMode(opts: BeginTransactionOpts): string | undefined { 73 | const accessMode = opts[1] 74 | if (this.database === 'sqlite' && accessMode) { 75 | throw new Error(this.database + " doesn't support the transactions access mode: " + accessMode) 76 | } 77 | if (!accessMode || accessMode === 'read write' || accessMode === 'read only') { 78 | return accessMode 79 | } 80 | throw new Error(this.database + " doesn't support the transactions access mode: " + accessMode) 81 | } 82 | createBeginTransactionQuery(opts: BeginTransactionOpts): string { 83 | let sql = 'begin transaction' 84 | const level = this.getTransactionLevel(opts) 85 | if (level) { 86 | sql += ' isolation level ' + level 87 | } 88 | const accessMode = this.getTransactionAccessMode(opts) 89 | if (accessMode) { 90 | if (sql) { 91 | sql += ', ' 92 | } 93 | sql += accessMode 94 | } 95 | return sql 96 | } 97 | } -------------------------------------------------------------------------------- /src/queryRunners/Sqlite3QueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { DatabaseType } from "./QueryRunner" 2 | import type { Database } from 'sqlite3' 3 | import { SqlTransactionQueryRunner } from "./SqlTransactionQueryRunner" 4 | 5 | export class Sqlite3QueryRunner extends SqlTransactionQueryRunner { 6 | readonly database: DatabaseType 7 | readonly connection: Database 8 | 9 | constructor(connection: Database) { 10 | super() 11 | this.connection = connection 12 | this.database = 'sqlite' 13 | } 14 | 15 | useDatabase(database: DatabaseType): void { 16 | if (database !== 'sqlite') { 17 | throw new Error('Unsupported database: ' + database + '. Sqlite3QueryRunner only supports sqlite databases') 18 | } 19 | } 20 | 21 | getNativeRunner(): Database { 22 | return this.connection 23 | } 24 | 25 | getCurrentNativeTransaction(): undefined { 26 | return undefined 27 | } 28 | 29 | execute(fn: (connection: unknown, transaction?: unknown) => Promise): Promise { 30 | return fn(this.connection) 31 | } 32 | 33 | protected executeQueryReturning(query: string, params: any[]): Promise { 34 | return new Promise((resolve, reject) => { 35 | this.connection.all(query, params, function (error, rows) { 36 | if (error) { 37 | reject(error) 38 | } else { 39 | resolve(rows) 40 | } 41 | }) 42 | }) 43 | } 44 | protected executeMutation(query: string, params: any[]): Promise { 45 | return new Promise((resolve, reject) => { 46 | this.connection.run(query, params, function (error) { 47 | if (error) { 48 | reject(error) 49 | } else { 50 | resolve(this.changes) 51 | } 52 | }) 53 | }) 54 | } 55 | executeInsertReturningLastInsertedId(query: string, params: any[] = []): Promise { 56 | if (this.containsInsertReturningClause(query, params)) { 57 | return super.executeInsertReturningLastInsertedId(query, params) 58 | } 59 | 60 | return new Promise((resolve, reject) => { 61 | this.connection.run(query, params, function (error) { 62 | if (error) { 63 | reject(error) 64 | } else { 65 | resolve(this.lastID) 66 | } 67 | }) 68 | }) 69 | } 70 | executeInsertReturningMultipleLastInsertedId(query: string, params: any[] = []): Promise { 71 | if (this.containsInsertReturningClause(query, params)) { 72 | return super.executeInsertReturningMultipleLastInsertedId(query, params) 73 | } 74 | throw new Error("Unsupported executeInsertReturningMultipleLastInsertedId on queries thar doesn't include the returning clause") 75 | } 76 | addParam(params: any[], value: any): string { 77 | params.push(value) 78 | return '?' 79 | } 80 | } -------------------------------------------------------------------------------- /src/queryRunners/Sqlite3WasmOO1QueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { DatabaseType, PromiseProvider } from "./QueryRunner" 2 | // @ts-ignore // TODO: remove when mjs conversion 3 | import type { Database } from '@sqlite.org/sqlite-wasm' 4 | import { SqlTransactionQueryRunner } from "./SqlTransactionQueryRunner" 5 | 6 | export interface Sqlite3WasmOO1QueryRunnerConfig { 7 | promise?: PromiseProvider 8 | } 9 | 10 | export class Sqlite3WasmOO1QueryRunner extends SqlTransactionQueryRunner { 11 | readonly database: DatabaseType 12 | readonly connection: Database 13 | readonly promise: PromiseProvider 14 | 15 | constructor(connection: Database, config?: Sqlite3WasmOO1QueryRunnerConfig) { 16 | super() 17 | this.connection = connection 18 | this.database = 'sqlite' 19 | this.promise = config?.promise || Promise 20 | } 21 | 22 | useDatabase(database: DatabaseType): void { 23 | if (database !== 'sqlite') { 24 | throw new Error('Unsupported database: ' + database + '. BetterSqlite3QueryRunner only supports sqlite databases') 25 | } 26 | } 27 | 28 | getNativeRunner(): Database { 29 | return this.connection 30 | } 31 | 32 | getCurrentNativeTransaction(): undefined { 33 | return undefined 34 | } 35 | 36 | execute(fn: (connection: unknown, transaction?: unknown) => Promise): Promise { 37 | return fn(this.connection) 38 | } 39 | 40 | protected executeQueryReturning(query: string, params: any[]): Promise { 41 | try { 42 | const rows = this.connection.selectObjects(query, params) 43 | return this.promise.resolve(rows) 44 | } catch (e) { 45 | return this.promise.reject(e) 46 | } 47 | } 48 | protected executeMutation(query: string, params: any[]): Promise { 49 | try { 50 | this.connection.exec({sql: query, bind: params}) 51 | return this.promise.resolve(this.connection.changes()) 52 | } catch (e) { 53 | return this.promise.reject(e) 54 | } 55 | } 56 | executeInsertReturningLastInsertedId(query: string, params: any[] = []): Promise { 57 | if (this.containsInsertReturningClause(query, params)) { 58 | return super.executeInsertReturningLastInsertedId(query, params) 59 | } 60 | 61 | try { 62 | this.connection.exec({sql: query, bind: params}) 63 | const id = this.connection.selectValue('select last_insert_rowid()') 64 | return this.promise.resolve(id) 65 | } catch (e) { 66 | return this.promise.reject(e) 67 | } 68 | } 69 | addParam(params: any[], value: any): string { 70 | if (typeof value === 'boolean') { 71 | params.push(Number(value)) 72 | } else { 73 | params.push(value) 74 | } 75 | return '?' 76 | } 77 | createResolvedPromise(result: RESULT): Promise { 78 | return this.promise.resolve(result) 79 | } 80 | createRejectedPromise(error: any): Promise { 81 | return this.promise.reject(error) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/queryRunners/SqliteQueryRunner.ts: -------------------------------------------------------------------------------- 1 | import type { DatabaseType } from "./QueryRunner" 2 | import type { Database } from 'sqlite' 3 | import { SqlTransactionQueryRunner } from "./SqlTransactionQueryRunner" 4 | 5 | export class SqliteQueryRunner extends SqlTransactionQueryRunner { 6 | readonly database: DatabaseType 7 | readonly connection: Database 8 | 9 | constructor(connection: Database) { 10 | super() 11 | this.connection = connection 12 | this.database = 'sqlite' 13 | } 14 | 15 | useDatabase(database: DatabaseType): void { 16 | if (database !== 'sqlite') { 17 | throw new Error('Unsupported database: ' + database + '. SqliteQueryRunner only supports sqlite databases') 18 | } 19 | } 20 | 21 | getNativeRunner(): Database { 22 | return this.connection 23 | } 24 | 25 | getCurrentNativeTransaction(): undefined { 26 | return undefined 27 | } 28 | 29 | execute(fn: (connection: unknown, transaction?: unknown) => Promise): Promise { 30 | return fn(this.connection) 31 | } 32 | 33 | protected executeQueryReturning(query: string, params: any[]): Promise { 34 | return this.connection.all(query, params) 35 | } 36 | protected executeMutation(query: string, params: any[]): Promise { 37 | return this.connection.run(query, params).then(result => result.changes!) 38 | } 39 | executeInsertReturningLastInsertedId(query: string, params: any[] = []): Promise { 40 | if (this.containsInsertReturningClause(query, params)) { 41 | return super.executeInsertReturningLastInsertedId(query, params) 42 | } 43 | 44 | return this.connection.run(query, params).then(result => result.lastID) 45 | } 46 | executeInsertReturningMultipleLastInsertedId(query: string, params: any[] = []): Promise { 47 | if (this.containsInsertReturningClause(query, params)) { 48 | return super.executeInsertReturningMultipleLastInsertedId(query, params) 49 | } 50 | throw new Error("Unsupported executeInsertReturningMultipleLastInsertedId on queries thar doesn't include the returning clause") 51 | } 52 | addParam(params: any[], value: any): string { 53 | params.push(value) 54 | return '?' 55 | } 56 | } -------------------------------------------------------------------------------- /src/sqlBuilders/NoopDBSqlBuilder.ts: -------------------------------------------------------------------------------- 1 | import type { TypeAdapter } from "../TypeAdapter" 2 | import { ValueType } from "../expressions/values" 3 | import { AbstractSqlBuilder } from "./AbstractSqlBuilder" 4 | 5 | export class NoopDBSqlBuilder extends AbstractSqlBuilder { 6 | noopDB: true = true 7 | 8 | _appendParam(value: any, params: any[], columnType: ValueType, columnTypeName: string, typeAdapter: TypeAdapter | undefined, forceTypeCast: boolean): string { 9 | return super._appendParam(value, params, columnType, columnTypeName, typeAdapter, forceTypeCast) + ':' + columnTypeName 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/Column.ts: -------------------------------------------------------------------------------- 1 | import type { AnyTableOrView } from "./ITableOrView" 2 | import type { ToSql } from "../sqlBuilders/SqlBuilder" 3 | import type { AnyValueSource, __ValueSourcePrivate } from "../expressions/values" 4 | import type { autogeneratedPrimaryKeyValue, hasDefaultValue, primaryKeyValue, type, writable } from "./symbols" 5 | import { isColumnObject } from "./symbols" 6 | 7 | // Represents any column in the database 8 | export interface DBColumn extends AnyValueSource { 9 | [type]: 'column' 10 | } 11 | 12 | export interface WritableDBColumn extends DBColumn { 13 | [writable]: true 14 | } 15 | 16 | export interface WritableDBPrimaryKeyColumn extends WritableDBColumn { 17 | [primaryKeyValue]: true 18 | } 19 | 20 | export interface WritableDBColumnWithDefaultValue extends WritableDBColumn { 21 | [hasDefaultValue]: true 22 | } 23 | 24 | export interface WritableDBColumnWithoutDefaultValue extends WritableDBColumn { 25 | [hasDefaultValue]: false 26 | } 27 | 28 | export interface WritableDBPrimaryKeyColumnWithDefaultValue extends WritableDBColumnWithDefaultValue, WritableDBPrimaryKeyColumn { 29 | [primaryKeyValue]: true 30 | [autogeneratedPrimaryKeyValue]: true 31 | } 32 | 33 | export interface WritableDBPrimaryKeyColumnWithoutDefaultValue extends WritableDBColumnWithoutDefaultValue, WritableDBPrimaryKeyColumn { 34 | [primaryKeyValue]: true 35 | [autogeneratedPrimaryKeyValue]: true 36 | } 37 | 38 | export interface Column extends WritableDBColumnWithoutDefaultValue { 39 | } 40 | 41 | export interface ColumnWithDefaultValue extends WritableDBColumnWithDefaultValue { 42 | } 43 | 44 | export interface PrimaryKeyColumn extends WritableDBPrimaryKeyColumnWithoutDefaultValue { 45 | [primaryKeyValue]: true 46 | } 47 | 48 | export interface PrimaryKeyAutogeneratedColumn extends WritableDBPrimaryKeyColumnWithDefaultValue { 49 | [primaryKeyValue]: true 50 | [autogeneratedPrimaryKeyValue]: true 51 | } 52 | 53 | export interface __ColumnPrivate extends __ValueSourcePrivate { 54 | [isColumnObject]: true 55 | __name: string 56 | __tableOrView: AnyTableOrView 57 | __hasDefault: boolean 58 | __isPrimaryKey: boolean 59 | __isAutogeneratedPrimaryKey: boolean 60 | __isComputed: boolean 61 | __sequenceName?: string 62 | __writable: boolean 63 | } 64 | 65 | export function isColumn(value: any): value is DBColumn { 66 | if (value === undefined || value === null) { 67 | return false 68 | } 69 | if (typeof value === 'object') { 70 | return !!value[isColumnObject] 71 | } 72 | return false 73 | } 74 | 75 | export function __getColumnPrivate(column: DBColumn): __ColumnPrivate { 76 | return column as any 77 | } 78 | 79 | export function __getColumnOfObject(obj: AnyTableOrView | { [property: string] : DBColumn}, column: string): (DBColumn & ToSql) | undefined { 80 | const result = (obj as any)[column] 81 | if (!result) { 82 | return undefined 83 | } 84 | if (typeof result !== 'object') { 85 | return undefined 86 | } 87 | if (result[isColumnObject]) { 88 | return result as any 89 | } else { 90 | return undefined 91 | } 92 | } -------------------------------------------------------------------------------- /src/utils/ConnectionConfiguration.ts: -------------------------------------------------------------------------------- 1 | export interface ConnectionConfiguration { 2 | allowEmptyString: boolean 3 | escape(identifier: string, strict: boolean): string 4 | insesitiveCollation?: string 5 | getDateTimeFormat?(type: string): string 6 | compatibilityMode?: boolean 7 | uuidStrategy?: string 8 | alwaysUseReturningClauseWhenInsert?: boolean 9 | } -------------------------------------------------------------------------------- /src/utils/IConnection.ts: -------------------------------------------------------------------------------- 1 | import type { NDB } from "./sourceName" 2 | import type { connection } from "./symbols" 3 | 4 | export interface IConnection { 5 | [connection]: DB 6 | } -------------------------------------------------------------------------------- /src/utils/RawFragment.ts: -------------------------------------------------------------------------------- 1 | import type { IRawFragment } from "./ITableOrView" 2 | import type { NSource } from "./sourceName" 3 | 4 | export interface RawFragment extends IRawFragment { 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/attachSource.ts: -------------------------------------------------------------------------------- 1 | export function attachSource(error: Error, source: Error): Error { 2 | Object.defineProperty(error, 'source', { 3 | value: source, 4 | writable: true, 5 | enumerable: false, 6 | configurable: true 7 | }) 8 | error.stack = error.stack + '\nSource: ' + source.stack 9 | return error 10 | } 11 | 12 | export function attachTransactionSource(error: Error, source: Error): Error { 13 | Object.defineProperty(error, 'transactionSource', { 14 | value: source, 15 | writable: true, 16 | enumerable: false, 17 | configurable: true 18 | }) 19 | error.stack = error.stack + '\nTransaction source: ' + source.stack 20 | return error 21 | } 22 | 23 | export function attachRollbackError(error: Error, source: unknown): Error { 24 | Object.defineProperty(error, 'rollbackError', { 25 | value: source, 26 | writable: true, 27 | enumerable: false, 28 | configurable: true 29 | }) 30 | if (source instanceof Error) { 31 | error.stack = error.stack + '\nRollback error: ' + source.stack 32 | } else { 33 | error.stack = error.stack + '\nRollback error: ' + source 34 | } 35 | return error 36 | } 37 | 38 | export function attachTransactionError(error: Error, source: unknown): Error { 39 | Object.defineProperty(error, 'transactionError', { 40 | value: source, 41 | writable: true, 42 | enumerable: false, 43 | configurable: true 44 | }) 45 | if (source instanceof Error) { 46 | error.stack = error.stack + '\nTransaction error: ' + source.stack 47 | } else { 48 | error.stack = error.stack + '\nTransaction error: ' + source 49 | } 50 | return error 51 | } 52 | 53 | export function attachAdditionalError(error: Error, additional: unknown, name: string): Error { 54 | let additionalErrors: Array | undefined = (error as any).additionalErrors 55 | if (!additionalErrors) { 56 | additionalErrors = [] 57 | Object.defineProperty(error, 'additionalErrors', { 58 | value: additionalErrors, 59 | writable: true, 60 | enumerable: false, 61 | configurable: true 62 | }) 63 | } 64 | additionalErrors.push(additional) 65 | if (additional instanceof Error) { 66 | error.stack = error.stack + '\n-------------------------------------------------------------\n' 67 | + 'An additional error happens during the ' + name + ' processing in another handler.\n' 68 | + 'Additional error: ' + additional.stack 69 | } else { 70 | error.stack = error.stack + '\n-------------------------------------------------------------\n' 71 | + 'An additional error happens during the ' + name + ' processing in another handler.\n' 72 | + 'Additional error: ' + additional 73 | } 74 | return error 75 | } -------------------------------------------------------------------------------- /src/utils/leftJoinUtils.ts: -------------------------------------------------------------------------------- 1 | import { __getValueSourcePrivate, isValueSource } from '../expressions/values' 2 | import { QueryColumns, isUsableValue } from '../sqlBuilders/SqlBuilder' 3 | 4 | export function __setColumnsForLeftJoin(columns: QueryColumns): void { 5 | for (const prop in columns) { 6 | const column = columns[prop]! 7 | if (!isUsableValue(prop, column, columns)) { 8 | continue 9 | } 10 | if (isValueSource(column)) { 11 | const columnPrivate = __getValueSourcePrivate(column) 12 | if (columnPrivate.__optionalType === 'required') { 13 | columnPrivate.__optionalType = 'originallyRequired' 14 | } 15 | } else { 16 | __setColumnsForLeftJoin(column) 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/utils/objectUtils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Force TS to show the real structure type 3 | * https://stackoverflow.com/questions/57683303/how-can-i-see-the-full-expanded-contract-of-a-typescript-type/57683652#57683652 4 | * 5 | * Recursive version cannot be used because it will cause expand over clases, like Date 6 | * There is no way to detect class instances: https://github.com/microsoft/TypeScript/issues/29063 7 | */ 8 | 9 | export type Expand = T extends object 10 | ? T extends infer O 11 | ? { [K in keyof O]: O[K] } 12 | : never 13 | : T 14 | 15 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 16 | 17 | /* 18 | * Dircard any key that is not a string or the value is a function 19 | */ 20 | 21 | export type UsableKeyOf = { [K in (keyof T) & string]: T[K] extends Function ? never : K}[(keyof T) & string] 22 | 23 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 24 | 25 | /* 26 | * Detect optional keys in a object 27 | * https://gist.github.com/eddiemoore/7873191f366675e520e802a9fb2531d8 28 | * 29 | * This works with exactOptionalPropertyTypes in tsconfig 30 | * 31 | * Only string keys will be returned, non-string keys are not used in ts-sql-query 32 | */ 33 | 34 | type Undefined = { [P in UsableKeyOf]: P extends undefined ? T[P] : never } 35 | 36 | type FilterFlags = { 37 | [Key in keyof Base]: Base[Key] extends Condition ? Key : never 38 | }; 39 | 40 | type AllowedNames = 41 | FilterFlags[keyof Base] 42 | 43 | type SubType = 44 | Pick> 45 | 46 | export type OptionalKeys = Exclude, never>>> 47 | export type RequiredKeys = NonNullable, never>> 48 | -------------------------------------------------------------------------------- /src/utils/resultUtils.ts: -------------------------------------------------------------------------------- 1 | export type MandatoryPropertiesOf = Exclude> // Do the substractio to deal with union types 2 | export type OptionalPropertiesOf = ({ [K in keyof TYPE]-?: null | undefined extends TYPE[K] ? K : (null extends TYPE[K] ? K : (undefined extends TYPE[K] ? K : never)) })[keyof TYPE] -------------------------------------------------------------------------------- /src/utils/symbols.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * All symbols defined here are expected to don't have value 3 | */ 4 | 5 | export const connection: unique symbol = Symbol('connection') 6 | export const source: unique symbol = Symbol('source') 7 | export const from: unique symbol = Symbol('from') 8 | export const using: unique symbol = Symbol('using') 9 | 10 | // Type marks 11 | export const isValueSourceObject: unique symbol = Symbol('isValueSourceObject') 12 | export const isTableOrViewObject: unique symbol = Symbol('isTableOrViewObject') 13 | export const isColumnObject: unique symbol = Symbol('isColumnObject') 14 | export const isSelectQueryObject: unique symbol = Symbol('isSelectQueryObject') 15 | 16 | // General Symbols 17 | export const type: unique symbol = Symbol('type') 18 | export const resultType: unique symbol = Symbol('resultType') 19 | export const columnsType: unique symbol = Symbol('columnsType') 20 | export const selectColumnsType: unique symbol = Symbol('selectColumnsType') 21 | export const compoundableColumns: unique symbol = Symbol('compoundableColumns') 22 | 23 | // Columns 24 | export const valueType: unique symbol = Symbol('valueType') 25 | export const writable: unique symbol = Symbol('valueType') 26 | export const optionalType: unique symbol = Symbol('optionalType') 27 | export const hasDefaultValue: unique symbol = Symbol('hasDefaultValue') 28 | export const autogeneratedPrimaryKeyValue: unique symbol = Symbol('autogeneratedPrimaryKeyValue') 29 | export const primaryKeyValue: unique symbol = Symbol('primaryKeyValue') 30 | 31 | // Value source type 32 | export const typeName: unique symbol = Symbol('typeName') 33 | export const nullableValueSource: unique symbol = Symbol('nullableValueSource') 34 | export const equalableValueSource: unique symbol = Symbol('equalableValueSource') 35 | export const comparableValueSource: unique symbol = Symbol('comparableValueSource') 36 | export const booleanValueSource: unique symbol = Symbol('booleanValueSource') 37 | export const ifValueSource: unique symbol = Symbol('ifValueSource') 38 | export const anyBooleanValueSource: unique symbol = Symbol('anyBooleanValueSource') 39 | export const numberValueSource: unique symbol = Symbol('numberValueSource') 40 | export const bigintValueSource: unique symbol = Symbol('bigintValueSource') 41 | export const customIntValueSource: unique symbol = Symbol('customIntValueSource') 42 | export const customDoubleValueSource: unique symbol = Symbol('customDoubleValueSource') 43 | export const stringValueSource: unique symbol = Symbol('stringValueSource') 44 | export const uuidValueSource: unique symbol = Symbol('uuidValueSource') 45 | export const customUuidValueSource: unique symbol = Symbol('customUuidValueSource') 46 | export const localDateValueSource: unique symbol = Symbol('dateValueSource') 47 | export const localTimeValueSource: unique symbol = Symbol('timeValueSource') 48 | export const localDateTimeValueSource: unique symbol = Symbol('dateTimeValueSource') 49 | export const customLocalDateValueSource: unique symbol = Symbol('customLocalDateValueSource') 50 | export const customLocalTimeValueSource: unique symbol = Symbol('customLocalTimeValueSource') 51 | export const customLocalDateTimeValueSource: unique symbol = Symbol('customLocalDateTimeValueSource') 52 | export const aggregatedArrayValueSource: unique symbol = Symbol('aggregatedArrayValueSource') 53 | 54 | // Opaque types 55 | export const dontCallConstructor: unique symbol = Symbol('dontCallConstructor') 56 | export const neverUsedSymbol: unique symbol = Symbol('neverUsedSymbol') 57 | 58 | // Transaction 59 | export const transactionIsolationLevel: unique symbol = Symbol('transactionIsolationLevel') -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # ts-sql-query test strategy 2 | 3 | ts-sql-query follows a strategy that can be a little bit different to what is usual: 4 | 5 | 1. The tests are structured as runnable examples located in the [src/examples folder](https://github.com/juanluispaz/ts-sql-query/tree/master/src/examples) 6 | 2. Tests must mimic real usage to be used as a reference for the users 7 | 3. All tests must be done using the public interface, like any user of ts-sql-query will use the library 8 | 4. The tests must run against real databases using real servers when it is possible 9 | 5. Tests that are executed in the real database are prefered over mock tests 10 | 6. The same test needs to be executed in each of the supported databases: 11 | - MariaDB 12 | - MySql 13 | - Oracle 14 | - PostgreSql 15 | - Sqlite 16 | - SqlServer 17 | 7. The same test needs to be executed using all the ways to connect to the database: 18 | - **The recommended one**: 19 | - better-sqlite3 (Sqlite) 20 | - mariadb (MariaDB) 21 | - mssql (SqlServer) 22 | - mysql2 (MySql) 23 | - oracledb (Oracle) 24 | - pg (PostgreSql) 25 | - sqlite3 (Sqlite) 26 | - **The alternative one**: 27 | - mysql (MySql) 28 | - prisma (MariaDB, MySql, PostgreSql, Sqlite, SqlServer) 29 | 8. The same test needs to be executed using all the supported query runners available to connect to a specific database 30 | 31 | ## Preparation to execute the tests 32 | 33 | - You need to have docker installed on your computer 34 | - You need 17 Gb of free space to download all the database docker image 35 | - You need 72 Gb of additional free space that the docker disk image will need 36 | - You need to configure docker to allow the disk image size limit as minimum of 72 Gb 37 | - You will need to download Oracle's Instant Client package (basic version). 38 | - Linux version: https://www.oracle.com/es/database/technologies/instant-client/linux-x86-64-downloads.html 39 | - MacOS version: https://www.oracle.com/es/database/technologies/instant-client/macos-intel-x86-downloads.html 40 | - You need to uncompress Oracle's Instant Client. 41 | 42 | **MacOS users**: 43 | 44 | The file must be uncompressed in a folder different to the standard folders included in your user directory. This rule doesn't allow to use of the `Download` folder. Suppose you place the content of Oracle's Instant Client in any of the standard folders available in your home directory. In that case, the MacOS's gatekeeper will stop you due to additional permissions required. 45 | 46 | When Oracle's Instant Client is uncompressed, you will need to drop the quarantine mark over the files. To do this, you must execute the command `xattr -d com.apple.quarantine *` inside the uncompressed folder. 47 | 48 | - You need to update the following line of the [scripts/run-all-examples.sh](https://github.com/juanluispaz/ts-sql-query/blob/master/scripts/run-all-examples.sh) with the location of the Oracle's Instant Client folder: 49 | 50 | **Before**: `cp -R -X $PWD/../instantclient_19_8/* node_modules/oracledb/build/Release` 51 | 52 | **After**: `cp -R -X /location/of/instantclient/* node_modules/oracledb/build/Release` 53 | 54 | - The first time, after clone the repository, you will need to execute the following commands: 55 | 56 | ``` 57 | npm install 58 | npm run generate-prisma 59 | ``` 60 | 61 | ## Running all tests 62 | 63 | To run all the tests, you need to execute the following command: 64 | 65 | `npm run all-examples` 66 | 67 | **Note**: The tests execution can last 13 min. 68 | 69 | ## Code coverage 70 | 71 | To run all the tests extracting the code coverage, you need to execute the following command: 72 | 73 | `npm run coverage` 74 | 75 | At the end, you will find the coverage report at `coverage/index.html` 76 | 77 | **Note**: The tests execution with coverage can last 13 min. -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "sourceMap": false, 7 | "newLine": "LF", 8 | "strict": true, 9 | "noFallthroughCasesInSwitch": true, 10 | "noImplicitReturns": true, 11 | "noUncheckedIndexedAccess": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "declaration": true, 15 | "isolatedModules": true, 16 | "outDir": "dist/", 17 | "rootDir": "src/", 18 | "noErrorTruncation": true 19 | }, 20 | "exclude": [ 21 | "node_modules", 22 | "test", 23 | "dist", 24 | "build", 25 | "build-test" 26 | ] 27 | } 28 | --------------------------------------------------------------------------------