├── .gitignore ├── .prettierrc ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── biome.json ├── dbschema ├── V1__create_test_fixture.sql ├── V2__create_users_posts_comments.sql ├── V3__create_tables_books_authors.sql └── V4__create_tables_clients_addresses.sql ├── docker-compose.yml ├── docs ├── functions.md ├── in_clause.md ├── insert.md ├── like.md ├── nested-query-result.md ├── orderBy_limit.md ├── orderby_error.png └── query_scaffolding.md ├── migrations └── postgres │ ├── V10__create_enum_types_table.sql │ ├── V1__create_initial_schema.sql │ ├── V2__create_users_posts_comments.sql │ ├── V3__create_tables_books_authors.sql │ ├── V4__create_tables_clients_addresses.sql │ ├── V5__seed_relations.sql │ ├── V6__seed.sql │ ├── V7__create_composite_constraint_table.sql │ ├── V8__all_types_add_enum_columns.sql │ └── V9__all_types_add_default_columns.sql ├── package-lock.json ├── package.json ├── sqlite-attached-migrations └── V1__create_tables.sql ├── sqlite-migrations ├── V10__generated_column.sql ├── V11__create-json-table.sql ├── V1__create_tables.sql ├── V2__create_users_posts_comments.sql ├── V3__create_tables_books_authors.sql ├── V4__create_tables_clients_addresses.sql ├── V5__create_tables_movies_actors_reviews.sql ├── V6__create_chinook_database.sql ├── V7__create_fts.sql ├── V8__create_enum_types_table.sql └── V9__seed_relations.sql ├── src ├── cli.ts ├── code-generator.ts ├── code-generator2.ts ├── describe-dynamic-query.ts ├── describe-nested-query.ts ├── describe-query.ts ├── dialects │ └── postgres.ts ├── drivers │ ├── libsql.ts │ ├── postgres.ts │ ├── sqlite.ts │ └── types.ts ├── mysql-mapping.ts ├── mysql-query-analyzer │ ├── collect-constraints.ts │ ├── infer-column-nullability.ts │ ├── infer-param-nullability.ts │ ├── parse.ts │ ├── select-columns.ts │ ├── traverse.ts │ ├── types.ts │ ├── unify.ts │ ├── util.ts │ └── verify-multiple-result.ts ├── postgres-query-analyzer │ ├── describe.ts │ ├── enum-parser.ts │ ├── parser.ts │ └── traverse.ts ├── queryExectutor.ts ├── schema-info.ts ├── sql-generator.ts ├── sqlite-query-analyzer │ ├── code-generator.ts │ ├── enum-parser.ts │ ├── parser.ts │ ├── query-executor.ts │ ├── replace-list-params.ts │ ├── sqlite-describe-nested-query.ts │ ├── traverse.ts │ ├── types.ts │ └── virtual-tables.ts ├── ts-dynamic-query-descriptor.ts ├── ts-nested-descriptor.ts ├── types.ts ├── util.ts └── utility-types.ts ├── tests ├── check-mysql-inference.ts ├── code-generator.test.ts ├── dynamic-query.test.ts ├── expected-code │ ├── dynamic-query01.ts.txt │ ├── dynamic-query02.ts.txt │ ├── dynamic-query03.ts.txt │ ├── dynamic-query04.ts.txt │ ├── dynamic-query05.ts.txt │ ├── dynamic-query06.ts.txt │ └── dynamic-query07.ts.txt ├── ext │ └── uuid.dll ├── infer-not-null-experimental.test.ts ├── infer-not-null.test.ts ├── load-schema.test.ts ├── mysql-query-analyzer │ ├── column-nullability-functions.test.ts │ ├── create-schema.ts │ ├── infer-column-nullability.test.ts │ ├── infer-param-nullability.test.ts │ ├── query-info.test.ts │ ├── test-substitution.test.ts │ ├── type-inference-functions.test.ts │ ├── type-inference-insert-stmt.test.ts │ ├── type-inference.test.ts │ └── utility-functions.test.ts ├── nested-query.test.ts ├── nested-ts-descriptor.test.ts ├── nested │ └── nested-info.test.ts ├── parse-delete.test.ts ├── parse-insert.test.ts ├── parse-params.test.ts ├── parse-select-complex-queries.test.ts ├── parse-select-functions.test.ts ├── parse-select-multiples-tables.test.ts ├── parse-select-single-table.test.ts ├── parse-select-subqueries.test.ts ├── parse-update.test.ts ├── parse-window-functions.test.ts ├── parse-with-comment.ts ├── postgres-e2e │ ├── dynamic-query.test.ts │ ├── nested-result.test.ts │ ├── sql │ │ ├── derivated-table.sql │ │ ├── derivated-table.ts │ │ ├── dynamic-query-01.sql │ │ ├── dynamic-query-01.ts │ │ ├── dynamic-query03.sql │ │ ├── dynamic-query03.ts │ │ ├── dynamic-query04.sql │ │ ├── dynamic-query04.ts │ │ ├── dynamic-query05.sql │ │ ├── dynamic-query05.ts │ │ ├── dynamic-query08.sql │ │ ├── dynamic-query08.ts │ │ ├── index.ts │ │ ├── nested01.sql │ │ ├── nested01.ts │ │ ├── nested02.sql │ │ ├── nested02.ts │ │ ├── nested03.sql │ │ ├── nested03.ts │ │ ├── nested04-without-join-table.sql │ │ ├── nested04-without-join-table.ts │ │ ├── nested04.sql │ │ └── nested04.ts │ └── typesql.json ├── postgres │ ├── code-generator.test.ts │ ├── describe-copy-stmt.test.ts │ ├── enum-parser.test.ts │ ├── expected-code │ │ ├── copy01.ts.txt │ │ ├── crud-delete01.ts.txt │ │ ├── crud-insert01.ts.txt │ │ ├── crud-select01.ts.txt │ │ ├── crud-update01.ts.txt │ │ ├── crud-update02.ts.txt │ │ ├── delete01.ts.txt │ │ ├── dynamic-query01.ts.txt │ │ ├── dynamic-query02.ts.txt │ │ ├── dynamic-query03.ts.txt │ │ ├── dynamic-query04.ts.txt │ │ ├── dynamic-query05.ts.txt │ │ ├── dynamic-query08-date.ts.txt │ │ ├── insert01.ts.txt │ │ ├── insert02.ts.txt │ │ ├── insert03.ts.txt │ │ ├── insert04-default.ts.txt │ │ ├── nested01.ts.txt │ │ ├── nested02-clients-with-addresses.ts.txt │ │ ├── nested03-many-to-many.ts.txt │ │ ├── select-type-cast.ts.txt │ │ ├── select01.ts.txt │ │ ├── select02.ts.txt │ │ ├── select03.ts.txt │ │ ├── select06-2.ts.txt │ │ ├── select06-any.ts.txt │ │ ├── select06.ts.txt │ │ ├── select08.ts.txt │ │ ├── select09-enum-constraint.ts.txt │ │ ├── select09-enum.ts.txt │ │ └── update01.ts.txt │ ├── generate-dynamic-info.test.ts │ ├── infer-column-nullability.test.ts │ ├── parse-delete.test.ts │ ├── parse-insert.test.ts │ ├── parse-select-complex-queries.test.ts │ ├── parse-select-functions.test.ts │ ├── parse-select-multiples-tables.test.ts │ ├── parse-select-single-table.test.ts │ ├── parse-select-subqueries.test.ts │ ├── parse-update.test.ts │ ├── parse-window-functions.test.ts │ ├── query-executor.test.ts │ ├── relation-info.test.ts │ ├── schema.ts │ └── type-mapping.test.ts ├── preprocess-sql.test.ts ├── query-executor.test.ts ├── rename-invalid-column-names.test.ts ├── sql-generator.test.ts ├── sqlite-e2e │ ├── sql │ │ ├── index.ts │ │ ├── nested01.sql │ │ ├── nested01.ts │ │ ├── nested02.sql │ │ ├── nested02.ts │ │ ├── nested03.sql │ │ ├── nested03.ts │ │ ├── nested04-without-join-table.sql │ │ ├── nested04-without-join-table.ts │ │ ├── nested04.sql │ │ └── nested04.ts │ └── sqlite-nested-result.test.ts ├── sqlite │ ├── enum-parser.test.ts │ ├── expected-code │ │ ├── crud-delete01-bun.ts.txt │ │ ├── crud-delete01-d1.ts.txt │ │ ├── crud-delete01-libsql.ts.txt │ │ ├── crud-delete01.ts.txt │ │ ├── crud-insert01-bun.ts.txt │ │ ├── crud-insert01-d1.ts.txt │ │ ├── crud-insert01-libsql.ts.txt │ │ ├── crud-insert01.ts.txt │ │ ├── crud-select01-bun.ts.txt │ │ ├── crud-select01-d1.ts.txt │ │ ├── crud-select01-libsql.ts.txt │ │ ├── crud-select01.ts.txt │ │ ├── crud-update01-bun.ts.txt │ │ ├── crud-update01-d1.ts.txt │ │ ├── crud-update01-libsql.ts.txt │ │ ├── crud-update01.ts.txt │ │ ├── delete01-bun.ts.txt │ │ ├── delete01-d1.ts.txt │ │ ├── delete01-libsql.ts.txt │ │ ├── delete01.ts.txt │ │ ├── dynamic-query01-bun.ts.txt │ │ ├── dynamic-query01-d1.ts.txt │ │ ├── dynamic-query01-libsql.ts.txt │ │ ├── dynamic-query01.ts.txt │ │ ├── dynamic-query02-bun.ts.txt │ │ ├── dynamic-query02-d1.ts.txt │ │ ├── dynamic-query02.ts.txt │ │ ├── dynamic-query03-bun.ts.txt │ │ ├── dynamic-query03.ts.txt │ │ ├── dynamic-query04.ts.txt │ │ ├── dynamic-query05.ts.txt │ │ ├── dynamic-query06.ts.txt │ │ ├── dynamic-query07.ts.txt │ │ ├── dynamic-query08-date.ts.txt │ │ ├── dynamic-query09-params-on-select.ts.txt │ │ ├── dynamic-query10-limit-offset.ts.txt │ │ ├── dynamic-query11.ts.txt │ │ ├── dynamic-query12.ts.txt │ │ ├── insert01-bun.ts.txt │ │ ├── insert01-d1.ts.txt │ │ ├── insert01-libsql.ts.txt │ │ ├── insert01.ts.txt │ │ ├── insert02-bun.ts.txt │ │ ├── insert02-d1.ts.txt │ │ ├── insert02.ts.txt │ │ ├── insert03-d1.ts.txt │ │ ├── insert03-libsql.ts.txt │ │ ├── nested01-bun.ts.txt │ │ ├── nested01-d1.ts.txt │ │ ├── nested01-libsql.ts.txt │ │ ├── nested01.ts.txt │ │ ├── nested02-bun-clients-with-addresses.ts.txt │ │ ├── nested02-clients-with-addresses.ts.txt │ │ ├── nested02-d1-clients-with-addresses.ts.txt │ │ ├── nested02.ts.txt │ │ ├── nested03-bun-many-to-many.ts.txt │ │ ├── nested03-libsql-many-to-many.ts.txt │ │ ├── select01-bun.ts.txt │ │ ├── select01-d1.ts.txt │ │ ├── select01-libsql.ts.txt │ │ ├── select01.ts.txt │ │ ├── select02-bun.ts.txt │ │ ├── select02-d1.ts.txt │ │ ├── select02-libsql.ts.txt │ │ ├── select02.ts.txt │ │ ├── select03-bun.ts.txt │ │ ├── select03-d1.ts.txt │ │ ├── select03.ts.txt │ │ ├── select04-bun.ts.txt │ │ ├── select04-d1.ts.txt │ │ ├── select04.ts.txt │ │ ├── select05-bun.ts.txt │ │ ├── select05-d1.ts.txt │ │ ├── select05.ts.txt │ │ ├── select06-bun.ts.txt │ │ ├── select06-d1.ts.txt │ │ ├── select06.ts.txt │ │ ├── select07-fts.ts.txt │ │ ├── select08-boolean.ts.txt │ │ ├── select09-bun-enum.ts.txt │ │ ├── select09-enum.ts.txt │ │ ├── select09-libsql-enum.ts.txt │ │ ├── update01-bun.ts.txt │ │ ├── update01-d1.ts.txt │ │ ├── update01-libsql.ts.txt │ │ └── update01.ts.txt │ ├── parse-insert.test.ts │ ├── parse-params.test.ts │ ├── parse-window-functions.test.ts │ ├── sqlite-code-generator.test.ts │ ├── sqlite-generate-dynamic-info.test.ts │ ├── sqlite-infer-not-null.test.ts │ ├── sqlite-infer-nullability.test.ts │ ├── sqlite-json-functions.test.ts │ ├── sqlite-load-extension.test.ts │ ├── sqlite-nested-query.test.ts │ ├── sqlite-parse-delete.test.ts │ ├── sqlite-parse-params.test.ts │ ├── sqlite-parse-select-complex-queries.test.ts │ ├── sqlite-parse-select-functions.test.ts │ ├── sqlite-parse-select-multiples-tables.test.ts │ ├── sqlite-parse-select-single-table.test.ts │ ├── sqlite-parse-select-subqueries.test.ts │ ├── sqlite-parse-update.test.ts │ ├── sqlite-query-executor.test.ts │ └── sqlite-teste-replace-list-params.test.ts └── type-mapping.test.ts ├── tsconfig.json ├── typesql-deno.gif ├── typesql-language-server.gif ├── typesql.json └── typesql.json.dist /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | *.db 4 | 5 | .vscode 6 | .idea 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 140 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 15 | "args": [ 16 | "-r", 17 | "ts-node/register", 18 | "${workspaceFolder}/tests/**/*.test.ts", 19 | ], 20 | "runtimeArgs": ["-r", "ts-node/register"], 21 | "outFiles": [ 22 | "${workspaceFolder}/**/*.js" 23 | ] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Wagner Porto 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 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", 3 | "organizeImports": { 4 | "enabled": false 5 | }, 6 | "linter": { 7 | "enabled": true, 8 | "rules": { 9 | "recommended": true, 10 | "complexity": { 11 | "noForEach": "off" 12 | }, 13 | "style": { 14 | "noNonNullAssertion": "off" 15 | }, 16 | "suspicious": { 17 | "noExplicitAny": "off" 18 | } 19 | } 20 | }, 21 | "javascript": { 22 | "formatter": { 23 | "quoteStyle": "single", 24 | "trailingCommas": "none" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /dbschema/V1__create_test_fixture.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `mytable1` ( 2 | `id` int NOT NULL AUTO_INCREMENT, 3 | `value` int, 4 | PRIMARY KEY (`id`) 5 | ) ENGINE=InnoDB; 6 | 7 | CREATE TABLE `mytable2` ( 8 | `id` int NOT NULL AUTO_INCREMENT, 9 | `name` varchar(100), 10 | `descr` varchar(100), 11 | PRIMARY KEY (`id`) 12 | ) ENGINE=InnoDB; 13 | 14 | CREATE TABLE `mytable3` ( 15 | `id` int NOT NULL AUTO_INCREMENT, 16 | `double_value` double, 17 | `name` varchar(50) NOT NULL, 18 | PRIMARY KEY (`id`) 19 | ) ENGINE=InnoDB; 20 | 21 | CREATE TABLE mydb.all_types ( 22 | decimal_column DECIMAL, -- code: 246 23 | tinyint_column TINYINT, -- code: 1 24 | smallint_column SMALLINT, -- code: 2 25 | int_column INT, -- code: 3 26 | float_column FLOAT, -- code: 4 27 | double_column DOUBLE, -- code: 5 28 | timestamp_column TIMESTAMP NULL, -- code: 7 29 | bigint_column BIGINT, -- code: 8 30 | mediumint_column MEDIUMINT, -- code: 9 31 | date_column DATE, -- code: 10 32 | time_column TIME, -- code: 11 33 | datetime_column DATETIME, -- code: 12 34 | year_column YEAR, -- code: 13 35 | varchar_column varchar(100), -- code: 253 36 | bit_column BIT, -- code: 16 37 | json_column json, -- code: 245 38 | enum_column ENUM('x-small', 'small', 'medium', 'large', 'x-large'), -- code: 254 WRONG? flags: 256 39 | set_column SET('a', 'b', 'c'), -- code: 254 WRONG? flags: 2048 40 | tinyblob_column TINYBLOB, -- code 249 41 | mediumblob_column MEDIUMBLOB, -- code 250 42 | longblob_column LONGBLOB, -- code 251 43 | blob_column BLOB, -- code 252 44 | tinytext_column TINYTEXT, -- code: 252 length: 765 45 | mediumtext_column MEDIUMTEXT, -- code: 252 length: 50331645 46 | longtext_column LONGTEXT, -- code: 252 length: 4294967295 47 | text_column TEXT(500), -- code: 252 length: 196605 48 | varbinary_column VARBINARY(200), -- code: 253 49 | binary_column BINARY(100), -- code: 254 (aka STRING, CHAR) 50 | char_column CHAR(4), -- code: 254 51 | geometry_column GEOMETRY -- code 255 52 | ) 53 | ENGINE=InnoDB; 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /dbschema/V2__create_users_posts_comments.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users( 2 | id SERIAL, 3 | name VARCHAR(255) NOT NULL, 4 | PRIMARY KEY `pk_id`(`id`) 5 | ); 6 | 7 | CREATE TABLE posts( 8 | id SERIAL, 9 | title varchar(255) NOT NULL, 10 | body TEXT NOT NULL, 11 | fk_user BIGINT UNSIGNED, 12 | CONSTRAINT `fk_posts_users` 13 | FOREIGN KEY (`fk_user`) 14 | REFERENCES `users` (`id`) 15 | ON DELETE NO ACTION 16 | ON UPDATE CASCADE, 17 | PRIMARY KEY `pk_id`(`id`) 18 | ); 19 | 20 | CREATE TABLE comments( 21 | id SERIAL, 22 | comment TEXT NOT NULL, 23 | fk_user BIGINT UNSIGNED NOT NULL, 24 | fk_post BIGINT UNSIGNED NOT NULL, 25 | CONSTRAINT `fk_comments_posts` 26 | FOREIGN KEY (`fk_post`) 27 | REFERENCES `posts` (`id`) 28 | ON DELETE NO ACTION 29 | ON UPDATE CASCADE, 30 | CONSTRAINT `fk_comments_users` 31 | FOREIGN KEY (`fk_user`) 32 | REFERENCES `users` (`id`) 33 | ON DELETE NO ACTION 34 | ON UPDATE CASCADE, 35 | PRIMARY KEY `pk_id`(`id`) 36 | ); 37 | 38 | CREATE TABLE roles( 39 | id SERIAL, 40 | role ENUM('user', 'admin', 'guest') NOT NULL DEFAULT 'user', 41 | fk_user BIGINT UNSIGNED NOT NULL, 42 | CONSTRAINT `fk_roles_users` 43 | FOREIGN KEY (`fk_user`) 44 | REFERENCES `users` (`id`) 45 | ON DELETE NO ACTION 46 | ON UPDATE CASCADE, 47 | PRIMARY KEY `pk_id`(`id`) 48 | ); 49 | 50 | CREATE TABLE surveys( 51 | id SERIAL, 52 | name VARCHAR(255) NOT NULL, 53 | PRIMARY KEY `pk_id`(`id`) 54 | ); 55 | 56 | CREATE TABLE participants( 57 | id SERIAL, 58 | fk_user BIGINT UNSIGNED NOT NULL, 59 | fk_survey BIGINT UNSIGNED NOT NULL, 60 | CONSTRAINT `fk_participant_users` 61 | FOREIGN KEY (`fk_user`) 62 | REFERENCES `users` (`id`) 63 | ON DELETE NO ACTION 64 | ON UPDATE CASCADE, 65 | CONSTRAINT `fk_participants_surveys` 66 | FOREIGN KEY (`fk_survey`) 67 | REFERENCES `surveys` (`id`) 68 | ON DELETE NO ACTION 69 | ON UPDATE CASCADE, 70 | PRIMARY KEY `pk_id`(`id`) 71 | ); 72 | 73 | CREATE TABLE questions( 74 | id SERIAL, 75 | questions VARCHAR(255) NOT NULL, 76 | fk_survey BIGINT UNSIGNED NOT NULL, 77 | CONSTRAINT `fk_questions_suvey` 78 | FOREIGN KEY (`fk_survey`) 79 | REFERENCES `surveys` (`id`) 80 | ON DELETE NO ACTION 81 | ON UPDATE CASCADE, 82 | PRIMARY KEY `pk_id`(`id`) 83 | ); 84 | 85 | CREATE TABLE answers( 86 | id SERIAL, 87 | answer VARCHAR(255), 88 | fk_user BIGINT UNSIGNED NOT NULL, 89 | fk_question BIGINT UNSIGNED NOT NULL, 90 | CONSTRAINT `fk_answers_users` 91 | FOREIGN KEY (`fk_user`) 92 | REFERENCES `users` (`id`) 93 | ON DELETE NO ACTION 94 | ON UPDATE CASCADE, 95 | CONSTRAINT `fk_answers_questions` 96 | FOREIGN KEY (`fk_question`) 97 | REFERENCES `questions` (`id`) 98 | ON DELETE NO ACTION 99 | ON UPDATE CASCADE, 100 | PRIMARY KEY `pk_id`(`id`) 101 | ); -------------------------------------------------------------------------------- /dbschema/V3__create_tables_books_authors.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE books( 2 | id SERIAL, 3 | title VARCHAR(255) NOT NULL, 4 | isbn char(50) NOT NULL, 5 | PRIMARY KEY `pk_id`(`id`) 6 | ); 7 | 8 | CREATE TABLE authors( 9 | id SERIAL, 10 | fullName VARCHAR(255) NOT NULL, 11 | shortName VARCHAR(50), 12 | PRIMARY KEY `pk_id`(`id`) 13 | ); 14 | 15 | CREATE TABLE books_authors( 16 | id SERIAL, 17 | book_id BIGINT UNSIGNED NOT NULL, 18 | author_id BIGINT UNSIGNED NOT NULL, 19 | author_ordinal INT NOT NULL, 20 | PRIMARY KEY `pk_id`(`id`), 21 | CONSTRAINT `fk_books_authors_books` 22 | FOREIGN KEY (`book_id`) 23 | REFERENCES `books` (`id`) 24 | ON DELETE NO ACTION 25 | ON UPDATE CASCADE, 26 | CONSTRAINT `fk_books_authors_authors` 27 | FOREIGN KEY (`author_id`) 28 | REFERENCES `authors` (`id`) 29 | ON DELETE NO ACTION 30 | ON UPDATE CASCADE 31 | ); -------------------------------------------------------------------------------- /dbschema/V4__create_tables_clients_addresses.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE addresses( 2 | id SERIAL, 3 | address VARCHAR(255) NOT NULL, 4 | PRIMARY KEY `pk_id`(`id`) 5 | ); 6 | 7 | CREATE TABLE clients( 8 | id SERIAL, 9 | primaryAddress BIGINT UNSIGNED NOT NULL, 10 | secondaryAddress BIGINT UNSIGNED, 11 | PRIMARY KEY `pk_id`(`id`), 12 | CONSTRAINT `fk_primaryAddress` 13 | FOREIGN KEY (`primaryAddress`) 14 | REFERENCES `addresses` (`id`) 15 | ON DELETE NO ACTION 16 | ON UPDATE CASCADE, 17 | CONSTRAINT `fk_secondaryAddress` 18 | FOREIGN KEY (`secondaryAddress`) 19 | REFERENCES `addresses` (`id`) 20 | ON DELETE NO ACTION 21 | ON UPDATE CASCADE 22 | ); -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | mysql-dev: 4 | # image: "mysql:5.7.41" 5 | image: "mysql:8.0.33" 6 | container_name: "mysql-dev" 7 | ports: 8 | - "3306:3306" 9 | command: --default-authentication-plugin=mysql_native_password 10 | environment: 11 | MYSQL_ROOT_PASSWORD: "password" 12 | MYSQL_DATABASE: "mydb" 13 | postgres-dev: 14 | image: "postgres:12.21" 15 | container_name: "postgres-dev" 16 | ports: 17 | - "5432:5432" 18 | environment: 19 | POSTGRES_USER: postgres 20 | POSTGRES_PASSWORD: password 21 | flyway: 22 | image: flyway/flyway:7.15 23 | container_name: "flyway" 24 | command: -url=jdbc:mysql://root:password@host.docker.internal:3306/mydb -connectRetries=60 migrate 25 | volumes: 26 | - ./dbschema:/flyway/sql 27 | depends_on: 28 | - mysql-dev 29 | sqlite-flyway: 30 | image: flyway/flyway:7.15 31 | container_name: "sqlite_flyway" 32 | command: -url=jdbc:sqlite:/flyway/db/mydb.db migrate 33 | volumes: 34 | - .:/flyway/db 35 | - ./sqlite-migrations:/flyway/sql 36 | sqlite-attached-flyway: 37 | image: flyway/flyway:7.15 38 | container_name: "sqlite_attached_flyway" 39 | command: -url=jdbc:sqlite:/flyway/db/users.db migrate 40 | volumes: 41 | - .:/flyway/db 42 | - ./sqlite-attached-migrations:/flyway/sql 43 | postgres-flyway: 44 | image: flyway/flyway:7.15 45 | container_name: "postgres_flyway" 46 | command: -url=jdbc:postgresql://postgres-dev:5432/postgres -schemas=public -user=postgres -password=password -connectRetries=60 migrate 47 | volumes: 48 | - ./migrations/postgres:/flyway/sql 49 | depends_on: 50 | - postgres-dev -------------------------------------------------------------------------------- /docs/functions.md: -------------------------------------------------------------------------------- 1 | Some of MySQL functions are supported by TypeSQL. 2 | 3 | ## Date and Time Functions 4 | 5 | Supported functions: ADDDATE, CURDATE, DATE_ADD, DATE_SUB, DATE_DIFF, DAY, HOUR, MICROSECOND, MINUTE, MONTH, NOW, SECOND, 6 | STR_TO_DATE, SUB_DATE, YEAR 7 | 8 | 9 | Example: 10 | 11 | If you have the SQL: 12 | ```sql 13 | SELECT :startDate, ADDDATE(:startDate, 20) as deadline 14 | ``` 15 | 16 | TypeSQL will generate the following types and function. 17 | 18 | ```typescript 19 | export type SelectDeadlineParams = { 20 | startDate: Date; 21 | } 22 | 23 | export type SelectDeadlineResult = { 24 | startDate: Date; 25 | deadline: Date; 26 | } 27 | 28 | export async function selectDeadline(client: Client, params: SelectDeadlineParams) : Promise { 29 | const sql = ` 30 | SELECT ? as startDate, ADDDATE(?, 20) as deadline 31 | ` 32 | 33 | return client.query(sql, [params.startDate]) 34 | .then( res => res ); 35 | } 36 | ``` 37 | 38 | And you can use execute the SQL as below. 39 | 40 | ```typescript 41 | const result = await selectDeadline(client, { 42 | startDate: new Date(2020, 2, 1) 43 | }) 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/in_clause.md: -------------------------------------------------------------------------------- 1 | The following query in `select-products-in-categories.sql`. 2 | 3 | ```sql 4 | SELECT 5 | ProductID, 6 | ProductName, 7 | UnitPrice 8 | FROM Products 9 | WHERE Discontinued = false 10 | AND CategoryID IN (?) 11 | ``` 12 | 13 | Will generate the following types and functions. 14 | 15 | ```typescript 16 | export type SelectProductsInCategoriesParams = { 17 | categories: number[]; 18 | } 19 | 20 | export type SelectProductsInCategoriesResult = { 21 | ProductID: number; 22 | ProductName: string; 23 | UnitPrice?: number; 24 | } 25 | 26 | export async function selectProductsInCategories(client: Client, params: SelectProductsInCategoriesParams) : Promise { 27 | const sql = ` 28 | SELECT 29 | ProductID, 30 | ProductName, 31 | UnitPrice 32 | FROM Products 33 | WHERE CategoryID IN (?) 34 | ` 35 | 36 | return client.query(sql, [params.categories]) 37 | .then( res => res ); 38 | } 39 | ``` 40 | 41 | And you can use the API as below. 42 | 43 | ```typescript 44 | const products = await selectProductsInCategories(client, { 45 | categories: [10, 11, 12] 46 | }) 47 | ``` 48 | 49 | You can also use the `NOT IN` Clause: 50 | 51 | ```sql 52 | SELECT 53 | ProductID, 54 | ProductName, 55 | UnitPrice 56 | FROM Products 57 | WHERE Discontinued = false 58 | AND CategoryID NOT IN (98, 99, ?) 59 | ``` 60 | **NOTE:** The IN Clause as shown will not work with the deno_mysql driver. See issue [#70](https://github.com/manyuanrong/deno_mysql/issues/70) in the deno_mysql repository. 61 | -------------------------------------------------------------------------------- /docs/insert.md: -------------------------------------------------------------------------------- 1 | Considere the following SQL in the file `insert-products.sql`. The template for this query can be generated using the cli command `typesql generate insert insert-product.sql --table Products`. 2 | 3 | ```sql 4 | INSERT INTO Products 5 | ( 6 | ProductName, 7 | SupplierID, 8 | CategoryID, 9 | QuantityPerUnit, 10 | UnitPrice, 11 | UnitsInStock, 12 | UnitsOnOrder, 13 | ReorderLevel, 14 | Discontinued 15 | ) 16 | VALUES 17 | ( 18 | :ProductName, 19 | :SupplierID, 20 | :CategoryID, 21 | :QuantityPerUnit, 22 | :UnitPrice, 23 | :UnitsInStock, 24 | :UnitsOnOrder, 25 | :ReorderLevel, 26 | :Discontinued 27 | ) 28 | ``` 29 | 30 | After run `typesql compile`, TypeSQL will generate the types and function below for this SQL. 31 | 32 | ```typescript 33 | export type InsertProductParams = { 34 | ProductName: string; 35 | SupplierID?: number; 36 | CategoryID?: number; 37 | QuantityPerUnit?: string; 38 | UnitPrice?: number; 39 | UnitsInStock?: number; 40 | UnitsOnOrder?: number; 41 | ReorderLevel?: number; 42 | Discontinued: boolean; 43 | } 44 | 45 | export type InsertProductResult = { 46 | affectedRows: number; 47 | insertId: number; 48 | } 49 | 50 | export async function insertProduct(client: Client, params: InsertProductParams) : Promise { 51 | const sql = ` 52 | INSERT INTO Products 53 | ( 54 | ProductName, 55 | SupplierID, 56 | CategoryID, 57 | QuantityPerUnit, 58 | UnitPrice, 59 | UnitsInStock, 60 | UnitsOnOrder, 61 | ReorderLevel, 62 | Discontinued 63 | ) 64 | VALUES 65 | ( 66 | ?, 67 | ?, 68 | ?, 69 | ?, 70 | ?, 71 | ?, 72 | ?, 73 | ?, 74 | ? 75 | ) 76 | ` 77 | 78 | return client.query(sql, [params.ProductName, params.SupplierID, params.CategoryID, params.QuantityPerUnit, params.UnitPrice, params.UnitsInStock, params.UnitsOnOrder, params.ReorderLevel, params.Discontinued]) 79 | .then( res => res ); 80 | } 81 | ``` 82 | 83 | You can use the generated code as following. Note that only `ProductName` and `Discontinued` are mandatory fields. 84 | 85 | ```typescript 86 | const result = await insertProduct(client, { 87 | ProductName: 'Product name', 88 | Discontinued: false 89 | }) 90 | 91 | console.log("insertedId:", result.insertId) 92 | ``` -------------------------------------------------------------------------------- /docs/like.md: -------------------------------------------------------------------------------- 1 | # Using LIKE Clause: 2 | 3 | The following SQL in the file `select-employees.sql`: 4 | 5 | ```sql 6 | SELECT 7 | employeeNumber, 8 | lastName, 9 | firstName 10 | FROM 11 | employees 12 | WHERE 13 | firstName LIKE 'a%' 14 | ``` 15 | 16 | Can be executed using the generated code: 17 | 18 | ```ts 19 | //... 20 | const result = await selectEmployees(conn); 21 | console.log("result=", result); 22 | 23 | ``` 24 | 25 | # Parameters in the LIKE Clause: 26 | 27 | ```sql 28 | SELECT 29 | employeeNumber, 30 | lastName, 31 | firstName 32 | FROM 33 | employees 34 | WHERE 35 | firstName LIKE :nameLike 36 | ``` 37 | 38 | ```ts 39 | //... 40 | const result = await selectEmployees(conn, { 41 | nameLike: 'a%' 42 | }); 43 | console.log("result=", result); 44 | ``` 45 | 46 | ```js 47 | result= [ 48 | { employeeNumber: 1143, lastName: 'Bow', firstName: 'Anthony' }, 49 | { employeeNumber: 1611, lastName: 'Fixter', firstName: 'Andy' } 50 | ] 51 | ``` -------------------------------------------------------------------------------- /docs/nested-query-result.md: -------------------------------------------------------------------------------- 1 | # Nested query results 2 | 3 | TypeSQL also has support for nested queries results. 4 | 5 | When you create your queries, by default, TypeSQL will generate a tabular result type, even if your queries include JOINs relations. For example, consider the query below in the file `select-user-posts.sql`: 6 | 7 | ```sql 8 | SELECT 9 | id as user_id, 10 | name as user_name, 11 | posts.id as post_id, 12 | title as post_title, 13 | body as post_body 14 | FROM users 15 | INNER JOIN posts on posts.user_id = users.id 16 | ``` 17 | 18 | For this query, TypeSQL by default will generate a type result like this: 19 | 20 | ```ts 21 | const result = await selectUserPosts(conn); 22 | 23 | //result type 24 | const result: { 25 | user_id: number; 26 | user_name: string; 27 | post_id: number; 28 | post_title: string; 29 | post_body: string; 30 | }[]; 31 | ``` 32 | 33 | If you want to generate a nested query result, you must annotate the query with `@nested` in a SQL comment. 34 | For example: 35 | 36 | ```sql 37 | -- @nested 38 | SELECT 39 | id, -- you must return the primary key of each relation 40 | name, 41 | posts.id, -- you must return the primary key of each relation 42 | title, 43 | body 44 | FROM users 45 | INNER JOIN posts on posts.user_id = users.id 46 | ``` 47 | 48 | Now TypeSQL will generate a nested type that will be returned when you run `selectUserPostsNested(conn)`: 49 | 50 | ```ts 51 | const result = await selectUserPostsNested(conn); 52 | 53 | //result type 54 | const result: { 55 | id: number; 56 | name: string; 57 | posts: { 58 | id: number; 59 | title: string; 60 | body: string; 61 | }[]; 62 | }[]; 63 | ``` 64 | 65 | ### Nested result limitations 66 | 67 | When you use nested queries you must project the `primary key` of each relation in the query. If you have a query like this `SELECT ... FROM users LEFT JOIN posts LEFT JOIN comments` you must project the primary of the tables: `users`, `posts` and `comments`. 68 | 69 | For example, the query below can't be annotated with ` @nested` because it doesn't project the primary key of the `posts` table (posts.id). 70 | 71 | ```sql 72 | -- This query can't be annotated with @nested 73 | SELECT 74 | id, 75 | name, 76 | title, 77 | body 78 | FROM users 79 | INNER JOIN posts on posts.user_id = users.id 80 | ``` 81 | -------------------------------------------------------------------------------- /docs/orderBy_limit.md: -------------------------------------------------------------------------------- 1 | # Order by and limit clauses 2 | 3 | You can add placeholders in the order by and limit clauses: 4 | 5 | ```sql 6 | SELECT 7 | emp_no, 8 | concat(first_name, ' ', last_name), 9 | year(hire_date) 10 | FROM employees 11 | ORDER BY ? 12 | LIMIT :offset, :limit 13 | 14 | ``` 15 | 16 | And can use the generated code as following: 17 | 18 | ```typescript 19 | const pageIndex = 0; 20 | const pageSize = 10; 21 | 22 | const employees = await selectEmployees(client, { 23 | offset: pageIndex * pageSize, 24 | limit: pageSize, 25 | orderBy: [ 26 | { 27 | column: `first_name`, 28 | direction: 'asc' 29 | }, 30 | { 31 | column: 'last_name', 32 | direction: 'asc' 33 | } 34 | ] 35 | }) 36 | ``` 37 | The code will fail at compile time if you try to pass an invalid order by parameter: 38 | 39 | ![Error in order by parameter](orderby_error.png) -------------------------------------------------------------------------------- /docs/orderby_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wsporto/typesql/6043ed7404bef17d2365e9109e3341d76de45c8d/docs/orderby_error.png -------------------------------------------------------------------------------- /docs/query_scaffolding.md: -------------------------------------------------------------------------------- 1 | Using the typesql cli to scaffold your sql queries can be a time saver. You can adjust them according your needs. 2 | 3 | Command: 4 | 5 | typesql **generate** *\* *\*\ 6 | typesql **g** *\* *\* 7 | 8 | |option|alias|example 9 | |------|-----|------- 10 | |select|s|`typesql generate select select-user.sql` 11 | |insert|i|`typesql generate insert insert-user.sql` 12 | |update|u|`typesql generate update update-user.sql` 13 | |delete|d|`typesql generate delete delete-user.sql` 14 | 15 | # Examples: 16 | 17 | ## SELECT: 18 | 19 | `typesql generate select select-employees.sql --table employees` will generate the following in the file `sqls/select-employees.sql`. 20 | ```sql 21 | SELECT 22 | emp_no, 23 | birth_date, 24 | first_name, 25 | last_name, 26 | gender, 27 | hire_date 28 | FROM employees 29 | ``` 30 | 31 | ## INSERT: 32 | 33 | `typesql generate insert insert-employee.sql --table employees` will generate the following in the file `sqls/insert-employee.sql`. 34 | ```sql 35 | INSERT INTO employees 36 | ( 37 | emp_no, 38 | birth_date, 39 | first_name, 40 | last_name, 41 | gender, 42 | hire_date 43 | ) 44 | VALUES 45 | ( 46 | :emp_no, 47 | :birth_date, 48 | :first_name, 49 | :last_name, 50 | :gender, 51 | :hire_date 52 | ) 53 | ``` 54 | 55 | ## UPDATE 56 | 57 | `typesql generate update update-employee.sql --table employees` will generate the following in the file `sqls/update-employee.sql`. 58 | ```sql 59 | UPDATE employees 60 | SET 61 | emp_no = :emp_no, 62 | birth_date = :birth_date, 63 | first_name = :first_name, 64 | last_name = :last_name, 65 | gender = :gender, 66 | hire_date = :hire_date 67 | WHERE 68 | emp_no = :emp_no 69 | ``` 70 | 71 | ## DELETE 72 | 73 | `typesql generate delete delete-employee.sql --table employees` will generate the following in the file `sqls/delete-employee.sql`. 74 | ```sql 75 | DELETE FROM employees 76 | WHERE emp_no = :emp_no 77 | ``` -------------------------------------------------------------------------------- /migrations/postgres/V10__create_enum_types_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE enum_types( 2 | id INTEGER PRIMARY KEY, 3 | column1 TEXT CHECK(column1 IN ('A', 'B', 'C')), 4 | column2 INTEGER CHECK(column2 IN (1, 2)), 5 | column3 TEXT CHECK(column3 NOT IN ('A', 'B', 'C')), 6 | column4 TEXT CHECK(column4 LIKE '%a%'), 7 | column5 TEXT NOT NULL CHECK(column5 IN ('D', 'E')), 8 | column6 INTEGER CHECK(column6 > 10) 9 | ); 10 | 11 | CREATE TABLE enum_types2( 12 | id INTEGER PRIMARY KEY, 13 | column1 TEXT CHECK ( column1 IN ('f', 'g') ) 14 | ); -------------------------------------------------------------------------------- /migrations/postgres/V1__create_initial_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE mytable1 ( 2 | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 3 | value INTEGER 4 | ); 5 | 6 | CREATE TABLE mytable2 ( 7 | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 8 | name TEXT, 9 | descr TEXT 10 | ); 11 | 12 | CREATE TABLE mytable3 ( 13 | id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 14 | double_value real, 15 | name TEXT NOT NULL 16 | ); 17 | 18 | CREATE TABLE mytable4 ( 19 | id TEXT PRIMARY KEY, 20 | name TEXT, 21 | year INTEGER 22 | ); 23 | 24 | CREATE TABLE mytable5 ( 25 | id INTEGER PRIMARY KEY, 26 | name TEXT, 27 | year INTEGER 28 | ); 29 | 30 | CREATE TABLE all_types ( 31 | bool_column BOOL, 32 | bytea_column BYTEA, 33 | char_column CHAR(1), 34 | name_column NAME, 35 | int8_column INT8, 36 | int2_column INT2, 37 | int4_column INT4, 38 | text_column TEXT, 39 | varchar_column VARCHAR(255), 40 | date_column DATE, 41 | bit_column BIT(1), 42 | numeric_column NUMERIC, 43 | uuid_column UUID, 44 | float4_column FLOAT4, 45 | float8_column FLOAT8, 46 | timestamp_column timestamp, 47 | timestamp_not_null_column timestamp NOT NULL, 48 | timestamptz_column timestamptz, 49 | timestamptz_not_null_column timestamptz NOT NULL 50 | ); 51 | -------------------------------------------------------------------------------- /migrations/postgres/V2__create_users_posts_comments.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users ( 2 | id SERIAL PRIMARY KEY, 3 | name TEXT NOT NULL, 4 | email TEXT 5 | ); 6 | 7 | CREATE TABLE posts ( 8 | id SERIAL PRIMARY KEY, 9 | title TEXT NOT NULL, 10 | body TEXT NOT NULL, 11 | fk_user INTEGER, 12 | FOREIGN KEY (fk_user) REFERENCES users (id) 13 | ); 14 | 15 | CREATE TABLE comments ( 16 | id SERIAL PRIMARY KEY, 17 | comment TEXT NOT NULL, 18 | fk_user INTEGER NOT NULL, 19 | fk_post INTEGER NOT NULL, 20 | FOREIGN KEY (fk_user) REFERENCES users (id), 21 | FOREIGN KEY (fk_post) REFERENCES posts (id) 22 | ); 23 | 24 | CREATE TABLE roles ( 25 | id SERIAL PRIMARY KEY, 26 | role TEXT NOT NULL DEFAULT 'user', 27 | fk_user INTEGER NOT NULL, 28 | FOREIGN KEY (fk_user) REFERENCES users (id) 29 | ); 30 | 31 | CREATE TABLE surveys ( 32 | id SERIAL PRIMARY KEY, 33 | name TEXT NOT NULL 34 | ); 35 | 36 | CREATE TABLE participants ( 37 | id SERIAL PRIMARY KEY, 38 | fk_user INTEGER NOT NULL, 39 | fk_survey INTEGER NOT NULL, 40 | FOREIGN KEY (fk_user) REFERENCES users (id), 41 | FOREIGN KEY (fk_survey) REFERENCES surveys (id) 42 | ); 43 | 44 | CREATE TABLE questions ( 45 | id SERIAL PRIMARY KEY, 46 | questions TEXT NOT NULL, 47 | fk_survey INTEGER NOT NULL, 48 | FOREIGN KEY (fk_survey) REFERENCES surveys(id) 49 | ); 50 | 51 | CREATE TABLE answers ( 52 | id SERIAL PRIMARY KEY, 53 | answer VARCHAR(255), 54 | fk_user INTEGER NOT NULL, 55 | fk_question INTEGER NOT NULL, 56 | FOREIGN KEY (fk_user) REFERENCES users (id), 57 | FOREIGN KEY (fk_question) REFERENCES questions(id) 58 | ); 59 | -------------------------------------------------------------------------------- /migrations/postgres/V3__create_tables_books_authors.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE books ( 2 | id SERIAL PRIMARY KEY, 3 | title TEXT NOT NULL, 4 | isbn TEXT NOT NULL 5 | ); 6 | 7 | CREATE TABLE authors ( 8 | id SERIAL PRIMARY KEY, 9 | fullName TEXT NOT NULL, 10 | shortName TEXT 11 | ); 12 | 13 | CREATE TABLE books_authors ( 14 | id SERIAL PRIMARY KEY, 15 | book_id BIGINT NOT NULL, 16 | author_id BIGINT NOT NULL, 17 | author_ordinal INT NOT NULL, 18 | FOREIGN KEY (book_id) REFERENCES books (id), 19 | FOREIGN KEY (author_id) REFERENCES authors (id) 20 | ); 21 | -------------------------------------------------------------------------------- /migrations/postgres/V4__create_tables_clients_addresses.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE addresses ( 2 | id SERIAL PRIMARY KEY, 3 | address TEXT NOT NULL 4 | ); 5 | 6 | CREATE TABLE clients ( 7 | id SERIAL PRIMARY KEY, 8 | primaryAddress INTEGER NOT NULL, 9 | secondaryAddress INTEGER, 10 | FOREIGN KEY (primaryAddress) REFERENCES addresses (id), 11 | FOREIGN KEY (secondaryAddress) REFERENCES addresses (id) 12 | ); 13 | -------------------------------------------------------------------------------- /migrations/postgres/V5__seed_relations.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO users( 2 | name 3 | ) VALUES 4 | ('user1'), 5 | ('user2'), 6 | ('user3'), 7 | ('user4'); 8 | 9 | INSERT INTO posts( 10 | title, 11 | body, 12 | fk_user 13 | ) VALUES 14 | ('title1', 'body1', 1), 15 | ('title2', 'body2', 1), 16 | ('title3', 'body3', 1), 17 | ('title4', 'body4', 3), 18 | ('title5', 'body5', 3), 19 | ('title6', 'body6', 4); 20 | 21 | INSERT INTO comments( 22 | comment, 23 | fk_user, 24 | fk_post 25 | ) VALUES 26 | ('comment1', 2, 1), -- post1, user2 27 | ('comment2', 3, 1), -- post1, user3 28 | ('comment3', 1, 3), -- post3, user1 29 | ('comment4', 1, 4), -- post4, user1 30 | ('comment5', 3, 4), -- post4, user3 31 | ('comment6', 4, 4), -- post4, user4 32 | ('comment7', 4, 5), -- post5, user4 33 | ('comment8', 1, 6); -- post6, user1 34 | 35 | INSERT INTO roles( 36 | role, 37 | fk_user 38 | ) VALUES 39 | ('role1', 1), --u1, role1 40 | ('role2', 1), --u1, role2 41 | ('role3', 2), --u2, role3 42 | ('role4', 3), --u3, role4 43 | ('role5', 4), --u4, role5 44 | ('role6', 4); --u4, role6 45 | 46 | INSERT INTO addresses( 47 | address 48 | ) VALUES 49 | ('address1'), 50 | ('address2'), 51 | ('address3'), 52 | ('address4'), 53 | ('address5'), 54 | ('address6'); 55 | 56 | INSERT INTO clients( 57 | primaryAddress, 58 | secondaryAddress 59 | ) 60 | VALUES 61 | (1, 2), -- c1, address1, address2 62 | (5, 6), -- c1, address5, address6 63 | (3, null), -- c3, address3 64 | (3, 4); -- c4, address3, address4 65 | 66 | 67 | INSERT INTO surveys( 68 | name 69 | ) VALUES 70 | ('s1'), 71 | ('s2'), 72 | ('s3'), 73 | ('s4'); 74 | 75 | -- user1: s1, s2 76 | -- user2: [] 77 | -- user3: s2, s3, s4 78 | -- user4: s3 79 | INSERT INTO participants( 80 | fk_user, 81 | fk_survey 82 | ) VALUES 83 | (1, 1), -- user1, s1 84 | (1, 2), -- user1, s2 85 | (3, 2), -- user3, s2 86 | (3, 3), -- user3, s3 87 | (3, 4), -- user3, s4 88 | (4, 3); -- user4, s3 89 | -------------------------------------------------------------------------------- /migrations/postgres/V6__seed.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO mytable1( 2 | value 3 | ) VALUES 4 | (1), 5 | (2), 6 | (3), 7 | (4); 8 | 9 | INSERT INTO mytable2( 10 | name, 11 | descr 12 | ) VALUES 13 | ('one', 'descr-one'), 14 | ('two', 'descr-two'), 15 | ('three', 'descr-three'), 16 | ('four', null); 17 | 18 | INSERT INTO public.all_types (bool_column,bytea_column,char_column,name_column,int8_column,int2_column,int4_column,text_column,varchar_column,date_column,bit_column,numeric_column,uuid_column,float4_column,float8_column,timestamp_column,timestamp_not_null_column,timestamptz_column,timestamptz_not_null_column) VALUES 19 | (NULL,NULL,NULL,NULL,NULL,NULL,1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'2024-12-31 00:00:00',NULL,'2024-12-31 00:00:00-03'), 20 | (NULL,NULL,NULL,NULL,NULL,NULL,2,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'2025-01-01 00:00:00',NULL,'2025-01-01 00:00:00-03'), 21 | (NULL,NULL,NULL,NULL,NULL,NULL,3,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'2025-01-02 00:00:00',NULL,'2025-01-02 00:00:00-03'), 22 | (NULL,NULL,NULL,NULL,NULL,NULL,4,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'2025-01-03 00:00:00',NULL,'2025-01-03 00:00:00-03'), 23 | (NULL,NULL,NULL,NULL,NULL,NULL,5,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'2025-02-04 00:00:00',NULL,'2025-02-04 00:00:00-03'), 24 | (NULL,NULL,NULL,NULL,NULL,NULL,6,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'2025-02-05 00:00:00',NULL,'2025-02-05 00:00:00-03'); 25 | -------------------------------------------------------------------------------- /migrations/postgres/V7__create_composite_constraint_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE composite_key ( 2 | key1 INTEGER, 3 | key2 INTEGER, 4 | value INTEGER, 5 | PRIMARY KEY (key1, key2) 6 | ); 7 | 8 | CREATE TABLE composite_unique_constraint ( 9 | key1 INTEGER, 10 | key2 INTEGER, 11 | value INTEGER, 12 | UNIQUE (key1, key2) 13 | ); -------------------------------------------------------------------------------- /migrations/postgres/V8__all_types_add_enum_columns.sql: -------------------------------------------------------------------------------- 1 | CREATE TYPE sizes_enum AS ENUM ('x-small', 'small', 'medium', 'large', 'x-large'); 2 | ALTER TABLE all_types 3 | ADD COLUMN enum_column sizes_enum, 4 | ADD COLUMN enum_constraint text CHECK (enum_constraint IN ('x-small', 'small', 'medium', 'large', 'x-large')); -------------------------------------------------------------------------------- /migrations/postgres/V9__all_types_add_default_columns.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE all_types 2 | ADD COLUMN integer_column_default INTEGER DEFAULT 10, 3 | ADD COLUMN enum_column_default sizes_enum DEFAULT 'medium', 4 | ADD COLUMN enum_constraint_default text CHECK (enum_constraint_default IN ('x-small', 'small', 'medium', 'large', 'x-large')) DEFAULT 'medium'; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typesql-cli", 3 | "version": "0.17.4", 4 | "description": "", 5 | "main": "index.js", 6 | "bin": { 7 | "typesql": "cli.js" 8 | }, 9 | "scripts": { 10 | "build": "tsc -w", 11 | "test": "mocha --enable-source-maps dist/tests/**/*.js --watch --watch-files **/*.js", 12 | "antlr4ts": "antlr4ts -Xexact-output-dir -o ./parser/ ./grammar/*.g4", 13 | "dist": "tsc && shx cp ./package.json ./README.md ./dist/src && cd ./dist/src && npm publish", 14 | "dist-exp": "tsc && shx cp ./package.json ./README.md ./dist/src && cd ./dist/src && npm publish --tag experimental", 15 | "gen": "node ./dist/src/cli.js compile", 16 | "gen-pg": "node ./dist/src/cli.js --config ./tests/postgres-e2e/typesql.json compile" 17 | }, 18 | "keywords": [], 19 | "author": "", 20 | "license": "MIT", 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "devDependencies": { 25 | "@biomejs/biome": "^1.9.4", 26 | "@cloudflare/workers-types": "^4.20250520.0", 27 | "@types/better-sqlite3": "^7.6.13", 28 | "@types/lodash.uniqby": "^4.7.9", 29 | "@types/mocha": "^10.0.10", 30 | "@types/node": "^20.17.49", 31 | "@types/pg": "^8.15.2", 32 | "@types/yargs": "^17.0.33", 33 | "mocha": "^10.8.2", 34 | "pg": "^8.16.0", 35 | "pg-copy-streams": "^6.0.6", 36 | "prettier": "^3.5.3", 37 | "shx": "^0.3.4", 38 | "ts-node": "^10.9.2", 39 | "typescript": "^5.8.3" 40 | }, 41 | "dependencies": { 42 | "@wsporto/typesql-parser": "^0.0.3", 43 | "better-sqlite3": "^11.10.0", 44 | "camelcase": "^6.3.0", 45 | "chokidar": "^3.6.0", 46 | "code-block-writer": "^13.0.3", 47 | "fp-ts": "^2.16.10", 48 | "glob": "^11.0.2", 49 | "libsql": "^0.4.7", 50 | "lodash.uniqby": "^4.7.0", 51 | "moment": "^2.30.1", 52 | "mysql2": "^3.14.1", 53 | "neverthrow": "^8.2.0", 54 | "postgres": "^3.4.5", 55 | "yargs": "^17.7.2" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /sqlite-attached-migrations/V1__create_tables.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users ( 2 | id INTEGER PRIMARY KEY, 3 | username TEXT NOT NULL UNIQUE 4 | ); -------------------------------------------------------------------------------- /sqlite-migrations/V10__generated_column.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE generated_column( 2 | id INTEGER PRIMARY KEY, 3 | first_name TEXT NOT NULL, 4 | last_name TEXT NOT NULL, 5 | full_name TEXT GENERATED ALWAYS AS (first_name || ' ' || last_name) 6 | ); 7 | -------------------------------------------------------------------------------- /sqlite-migrations/V11__create-json-table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE json_table( 2 | id INTEGER PRIMARY KEY, 3 | json_value TEXT NOT NULL, 4 | optional_json_value TEXT 5 | ); 6 | -------------------------------------------------------------------------------- /sqlite-migrations/V1__create_tables.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE mytable1 ( 2 | id INTEGER PRIMARY KEY AUTOINCREMENT, 3 | value INTEGER 4 | ); 5 | 6 | CREATE TABLE mytable2 ( 7 | id INTEGER PRIMARY KEY AUTOINCREMENT, 8 | name TEXT, 9 | descr TEXT 10 | ); 11 | 12 | CREATE TABLE mytable3 ( 13 | id INTEGER PRIMARY KEY AUTOINCREMENT, 14 | double_value REAL, 15 | name TEXT NOT NULL 16 | ); 17 | 18 | CREATE TABLE mytable4( 19 | id TEXT PRIMARY KEY, 20 | name TEXT, 21 | year INTEGER 22 | ); 23 | 24 | CREATE TABLE all_types ( 25 | int_column INT, 26 | integer_column INTEGER, 27 | tinyiny_column TINYINT, 28 | smallint_column SMALLINT, 29 | mediumint_column MEDIUMINT, 30 | bigint_column BIGINT, 31 | unsignedbigint_column UNSIGNED BIGINT, 32 | int2_column INT2, 33 | int8_column INT8, 34 | character_column CHARACTER(20), 35 | varchar_column VARCHAR(255), 36 | varyingcharacter_column VARYING CHARACTER(255), 37 | nchar_column NCHAR(55), 38 | native_character_column NATIVE CHARACTER(70), 39 | nvarchar_column NVARCHAR(100), 40 | text_column TEXT, 41 | clob_column CLOB, 42 | blob_column BLOB, 43 | blob_column2, 44 | real_column REAL, 45 | double_column DOUBLE, 46 | doubleprecision_column DOUBLE PRECISION, 47 | float_column FLOAT, 48 | numeric_column NUMERIC, 49 | decimal_column DECIMAL(10,5), 50 | boolean_column BOOLEAN, 51 | date_column DATE, 52 | datetime_column DATETIME, 53 | integer_column_default INTEGER DEFAULT 10 54 | ); -------------------------------------------------------------------------------- /sqlite-migrations/V2__create_users_posts_comments.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users( 2 | id INTEGER PRIMARY KEY AUTOINCREMENT, 3 | name TEXT NOT NULL 4 | ); 5 | 6 | CREATE TABLE posts( 7 | id INTEGER PRIMARY KEY AUTOINCREMENT, 8 | title TEXT NOT NULL, 9 | body TEXT NOT NULL, 10 | fk_user INTEGER 11 | ); 12 | 13 | CREATE TABLE comments( 14 | id INTEGER PRIMARY KEY AUTOINCREMENT, 15 | comment TEXT NOT NULL, 16 | fk_user INTEGER NOT NULL, 17 | fk_post INTEGER NOT NULL 18 | ); 19 | 20 | CREATE TABLE roles( 21 | id INTEGER PRIMARY KEY AUTOINCREMENT, 22 | role TEXT NOT NULL DEFAULT 'user', 23 | fk_user INTEGER NOT NULL 24 | ); 25 | 26 | CREATE TABLE surveys( 27 | id INTEGER PRIMARY KEY AUTOINCREMENT, 28 | name TEXT NOT NULL 29 | ); 30 | 31 | CREATE TABLE participants( 32 | id INTEGER PRIMARY KEY AUTOINCREMENT, 33 | fk_user INTEGER NOT NULL, 34 | fk_survey INTEGER NOT NULL, 35 | FOREIGN KEY (fk_user) REFERENCES users (id), 36 | FOREIGN KEY (fk_survey) REFERENCES surveys (id) 37 | ); 38 | 39 | CREATE TABLE questions( 40 | id INTEGER PRIMARY KEY AUTOINCREMENT, 41 | questions TEXT NOT NULL, 42 | fk_survey INTEGER NOT NULL, 43 | FOREIGN KEY (fk_survey) REFERENCES surveys(id) 44 | ); 45 | 46 | CREATE TABLE answers( 47 | id INTEGER PRIMARY KEY AUTOINCREMENT, 48 | answer VARCHAR(255), 49 | fk_user INTEGER NOT NULL, 50 | fk_question INTEGER NOT NULL, 51 | FOREIGN KEY (fk_user) REFERENCES users (id), 52 | FOREIGN KEY (fk_question) REFERENCES questions(id) 53 | ); -------------------------------------------------------------------------------- /sqlite-migrations/V3__create_tables_books_authors.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE books( 2 | id INTEGER PRIMARY KEY AUTOINCREMENT, 3 | title TEXT NOT NULL, 4 | isbn TEXT NOT NULL 5 | ); 6 | 7 | CREATE TABLE authors( 8 | id INTEGER PRIMARY KEY AUTOINCREMENT, 9 | fullName TEXT NOT NULL, 10 | shortName TEXT 11 | ); 12 | 13 | CREATE TABLE books_authors( 14 | id INTEGER PRIMARY KEY AUTOINCREMENT, 15 | book_id BIGINT UNSIGNED NOT NULL, 16 | author_id BIGINT UNSIGNED NOT NULL, 17 | author_ordinal INT NOT NULL, 18 | FOREIGN KEY (book_id) REFERENCES books (id), 19 | FOREIGN KEY (author_id) REFERENCES authors (id) 20 | ); -------------------------------------------------------------------------------- /sqlite-migrations/V4__create_tables_clients_addresses.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE addresses( 2 | id INTEGER PRIMARY KEY, 3 | address TEXT NOT NULL 4 | ); 5 | 6 | CREATE TABLE clients( 7 | id INTEGER PRIMARY KEY, 8 | primaryAddress INTEGER NOT NULL, 9 | secondaryAddress INTEGER, 10 | FOREIGN KEY(primaryAddress) REFERENCES addresses(id), 11 | FOREIGN KEY(secondaryAddress) REFERENCES addresses(id) 12 | ); -------------------------------------------------------------------------------- /sqlite-migrations/V5__create_tables_movies_actors_reviews.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS movies( 2 | id INTEGER PRIMARY KEY, 3 | title TEXT NOT NULL 4 | ); 5 | 6 | CREATE TABLE IF NOT EXISTS persons( 7 | id INTEGER PRIMARY KEY, 8 | name TEXT NOT NULL 9 | ); 10 | 11 | CREATE TABLE IF NOT EXISTS actors( 12 | id INTEGER PRIMARY KEY, 13 | person_id INTEGER NOT NULL UNIQUE, 14 | movie_id INTEGER NOT NULL, 15 | FOREIGN KEY (person_id) REFERENCES persons (id) 16 | FOREIGN KEY (movie_id) REFERENCES movies (id) 17 | ); 18 | 19 | CREATE TABLE IF NOT EXISTS reviews( 20 | id INTEGER PRIMARY KEY, 21 | rating INTEGER NOT NULL, 22 | movie_id INTEGER NOT NULL, 23 | FOREIGN KEY (movie_id) REFERENCES movies (id) 24 | ); -------------------------------------------------------------------------------- /sqlite-migrations/V7__create_fts.sql: -------------------------------------------------------------------------------- 1 | CREATE VIRTUAL TABLE mytable2_fts 2 | USING fts5 ( 3 | id, name, descr 4 | ); -------------------------------------------------------------------------------- /sqlite-migrations/V8__create_enum_types_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE enum_types( 2 | id INTEGER PRIMARY KEY, 3 | column1 TEXT CHECK(column1 IN ('A', 'B', 'C')), 4 | column2 INTEGER CHECK(column2 IN (1, 2)), 5 | column3 TEXT CHECK(column3 NOT IN ('A', 'B', 'C')), 6 | column4 TEXT CHECK(column4 LIKE '%a%'), 7 | column5 NOT NULL CHECK(column5 IN ('D', 'E')) 8 | ); 9 | 10 | CREATE TABLE enum_types2( 11 | id INTEGER PRIMARY KEY, 12 | column1 TEXT CHECK ( column1 IN ('f', 'g') ) 13 | ); 14 | 15 | ALTER TABLE all_types ADD COLUMN enum_column TEXT CHECK(enum_column IN ('x-small', 'small', 'medium', 'large', 'x-large')); -------------------------------------------------------------------------------- /sqlite-migrations/V9__seed_relations.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO users( 2 | id, 3 | name 4 | ) VALUES 5 | (1, 'user1'), 6 | (2, 'user2'), 7 | (3, 'user3'), 8 | (4, 'user4'); 9 | 10 | INSERT INTO posts( 11 | id, 12 | title, 13 | body, 14 | fk_user 15 | ) VALUES 16 | (1, 'title1', 'body1', 1), 17 | (2, 'title2', 'body2', 1), 18 | (3, 'title3', 'body3', 1), 19 | (4, 'title4', 'body4', 3), 20 | (5, 'title5', 'body5', 3), 21 | (6, 'title6', 'body6', 4); 22 | 23 | INSERT INTO comments( 24 | id, 25 | comment, 26 | fk_user, 27 | fk_post 28 | ) VALUES 29 | (1, 'comment1', 2, 1), -- post1, user2 30 | (2, 'comment2', 3, 1), -- post1, user3 31 | (3, 'comment3', 1, 3), -- post3, user1 32 | (4, 'comment4', 1, 4), -- post4, user1 33 | (5, 'comment5', 3, 4), -- post4, user3 34 | (6, 'comment6', 4, 4), -- post4, user4 35 | (7, 'comment7', 4, 5), -- post5, user4 36 | (8, 'comment8', 1, 6); -- post6, user1 37 | 38 | INSERT INTO roles( 39 | id, 40 | role, 41 | fk_user 42 | ) VALUES 43 | (1, 'role1', 1), --u1, role1 44 | (2, 'role2', 1), --u1, role2 45 | (3, 'role3', 2), --u2, role3 46 | (4, 'role4', 3), --u3, role4 47 | (5, 'role5', 4), --u4, role5 48 | (6, 'role6', 4); --u4, role6 49 | 50 | INSERT INTO addresses( 51 | id, 52 | address 53 | ) VALUES 54 | (1, 'address1'), 55 | (2, 'address2'), 56 | (3, 'address3'), 57 | (4, 'address4'), 58 | (5, 'address5'), 59 | (6, 'address6'); 60 | 61 | INSERT INTO clients( 62 | id, 63 | primaryAddress, 64 | secondaryAddress 65 | ) 66 | VALUES 67 | (1, 1, 2), -- c1, address1, address2 68 | (2, 5, 6), -- c1, address5, address6 69 | (3, 3, null), -- c3, address3 70 | (4, 3, 4); -- c4, address3, address4 71 | 72 | 73 | INSERT INTO surveys( 74 | id, 75 | name 76 | ) VALUES 77 | (1, 's1'), 78 | (2, 's2'), 79 | (3, 's3'), 80 | (4, 's4'); 81 | 82 | -- user1: s1, s2 83 | -- user2: [] 84 | -- user3: s2, s3, s4 85 | -- user4: s3 86 | INSERT INTO participants( 87 | id, 88 | fk_user, 89 | fk_survey 90 | ) VALUES 91 | (1, 1, 1), -- user1, s1 92 | (2, 1, 2), -- user1, s2 93 | (3, 3, 2), -- user3, s2 94 | (4, 3, 3), -- user3, s3 95 | (5, 3, 4), -- user3, s4 96 | (6, 4, 3); -- user4, s3 97 | -------------------------------------------------------------------------------- /src/drivers/libsql.ts: -------------------------------------------------------------------------------- 1 | import { ok, Result } from 'neverthrow'; 2 | import type { DatabaseClient, TypeSqlError } from '../types'; 3 | import Database from 'libsql'; 4 | 5 | export function createLibSqlClient(url: string, attachList: string[], loadExtensions: string[], authToken: string): Result { 6 | const opts = { 7 | authToken: authToken 8 | } as any; 9 | 10 | const db = new Database(url, opts); 11 | for (const attach of attachList) { 12 | db.exec(`attach database ${attach}`); 13 | } 14 | for (const extension of loadExtensions) { 15 | db.loadExtension(extension); 16 | } 17 | 18 | return ok({ 19 | type: 'libsql', 20 | client: db 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /src/drivers/sqlite.ts: -------------------------------------------------------------------------------- 1 | import { TsType } from '../mysql-mapping'; 2 | import { SQLiteType } from '../sqlite-query-analyzer/types'; 3 | import { SQLiteClient } from '../types'; 4 | 5 | export function mapColumnType(sqliteType: SQLiteType, client: SQLiteClient): TsType { 6 | switch (sqliteType) { 7 | case 'INTEGER': 8 | return 'number'; 9 | case 'INTEGER[]': 10 | return 'number[]'; 11 | case 'TEXT': 12 | return 'string'; 13 | case 'TEXT[]': 14 | return 'string[]'; 15 | case 'NUMERIC': 16 | return 'number'; 17 | case 'NUMERIC[]': 18 | return 'number[]'; 19 | case 'REAL': 20 | return 'number'; 21 | case 'REAL[]': 22 | return 'number[]'; 23 | case 'DATE': 24 | return 'Date'; 25 | case 'DATE_TIME': 26 | return 'Date'; 27 | case 'BLOB': 28 | return client === 'better-sqlite3' ? 'Uint8Array' : 'ArrayBuffer'; 29 | case 'BOOLEAN': 30 | return 'boolean'; 31 | } 32 | if (sqliteType.startsWith('ENUM')) { 33 | const enumValues = sqliteType.substring(sqliteType.indexOf('(') + 1, sqliteType.indexOf(')')); 34 | return enumValues.split(',').join(' | ') as TsType; 35 | } 36 | return 'any'; 37 | } -------------------------------------------------------------------------------- /src/drivers/types.ts: -------------------------------------------------------------------------------- 1 | import { TaskEither } from 'fp-ts/lib/TaskEither'; 2 | import { CheckConstraintResult, EnumMap } from './postgres'; 3 | 4 | export type PostgresColumnSchema = { 5 | oid: number; 6 | table_schema: string; 7 | table_name: string; 8 | column_name: string; 9 | type_id: number; 10 | is_nullable: boolean; 11 | column_key: 'PRI' | 'UNI' | ''; 12 | autoincrement?: boolean; 13 | column_default?: true; 14 | }; 15 | 16 | export type DescribeQueryColumn = { 17 | name: string; 18 | tableId: number; 19 | typeId: number; 20 | 21 | } 22 | 23 | export type DescribeParameters = { 24 | sql: string; 25 | postgresDescribeResult: PostgresDescribe; 26 | dbSchema: PostgresColumnSchema[]; 27 | enumsTypes: EnumMap; 28 | checkConstraints: CheckConstraintResult; 29 | namedParameters: string[]; 30 | } 31 | 32 | export type PostgresDescribe = { 33 | parameters: number[]; 34 | columns: DescribeQueryColumn[]; 35 | } 36 | export type DescribeQueryResult = PostgresDescribe & { 37 | multipleRowsResult: boolean; 38 | } 39 | 40 | export type PostgresType = { [key: number]: string } 41 | 42 | export type Driver = { 43 | loadDbSchema: (conn: Connection) => TaskEither; 44 | } -------------------------------------------------------------------------------- /src/mysql-query-analyzer/util.ts: -------------------------------------------------------------------------------- 1 | export type ParamIndexes = { 2 | paramName: string; 3 | indexes: number[]; 4 | }; 5 | 6 | export function getParameterIndexes(namedParameters: string[]): ParamIndexes[] { 7 | const hashMap: Map = new Map(); 8 | namedParameters.forEach((param, index) => { 9 | if (hashMap.has(param)) { 10 | hashMap.get(param)!.push(index); 11 | } else { 12 | hashMap.set(param, [index]); 13 | } 14 | }); 15 | 16 | const paramIndex = Array.from(hashMap.keys()).map((paramName) => { 17 | const paramIndexes: ParamIndexes = { 18 | paramName, 19 | indexes: hashMap.get(paramName)! 20 | }; 21 | return paramIndexes; 22 | }); 23 | 24 | return paramIndex; 25 | } 26 | 27 | export function getPairWise(indexes: number[], func: (cur: number, next: number) => void) { 28 | for (let i = 0; i < indexes.length - 1; i++) { 29 | func(indexes[i], indexes[i + 1]); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/mysql-query-analyzer/verify-multiple-result.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type ExprContext, 3 | ExprIsContext, 4 | PrimaryExprCompareContext, 5 | ExprAndContext, 6 | SimpleExprColumnRefContext, 7 | ExprNotContext, 8 | ExprOrContext, 9 | type BoolPriContext, 10 | type PredicateContext 11 | } from '@wsporto/typesql-parser/mysql/MySQLParser'; 12 | import { getSimpleExpressions, splitName, findColumn } from './select-columns'; 13 | import type { ColumnDef } from './types'; 14 | 15 | export function verifyMultipleResult(exprContext: ExprContext, fromColumns: ColumnDef[]): boolean { 16 | if (exprContext instanceof ExprIsContext) { 17 | const boolPri = exprContext.boolPri(); 18 | 19 | if (boolPri instanceof PrimaryExprCompareContext) { 20 | if (boolPri.compOp().EQUAL_OPERATOR()) { 21 | const compareLeft = boolPri.boolPri(); 22 | const compareRight = boolPri.predicate(); 23 | if (isUniqueKeyComparation(compareLeft, fromColumns) || isUniqueKeyComparation(compareRight, fromColumns)) { 24 | return false; //multipleRow = false 25 | } 26 | } 27 | return true; //multipleRow = true 28 | } 29 | return true; //multipleRow 30 | } 31 | if (exprContext instanceof ExprNotContext) { 32 | return true; 33 | } 34 | if (exprContext instanceof ExprAndContext) { 35 | const oneIsSingleResult = exprContext.expr_list().some((expr) => verifyMultipleResult(expr, fromColumns) === false); 36 | return oneIsSingleResult === false; 37 | } 38 | // if (exprContext instanceof ExprXorContext) { 39 | // const expressions = exprContext.expr(); 40 | // } 41 | if (exprContext instanceof ExprOrContext) { 42 | return true; //multipleRow = true 43 | } 44 | 45 | throw Error(`Unknow type:${exprContext.constructor.name}`); 46 | } 47 | 48 | function isUniqueKeyComparation(compare: BoolPriContext | PredicateContext, fromColumns: ColumnDef[]) { 49 | const tokens = getSimpleExpressions(compare); 50 | if (tokens.length === 1 && tokens[0] instanceof SimpleExprColumnRefContext) { 51 | const fieldName = splitName(tokens[0].getText()); 52 | const col = findColumn(fieldName, fromColumns); 53 | if (col.columnKey === 'PRI' || col.columnKey === 'UNI') { 54 | //TODO - UNIQUE 55 | return true; //isUniqueKeyComparation = true 56 | } 57 | } 58 | return false; //isUniqueKeyComparation = false 59 | } 60 | -------------------------------------------------------------------------------- /src/postgres-query-analyzer/enum-parser.ts: -------------------------------------------------------------------------------- 1 | export function transformCheckToEnum(checkStr: string): string | null { 2 | // Match only = ANY (ARRAY[...]) pattern 3 | const anyArrayRegex = /=\s*ANY\s*\(\s*ARRAY\[(.*?)\]\s*\)/i; 4 | const match = checkStr.match(anyArrayRegex); 5 | 6 | if (!match || match.length < 2) { 7 | return null; 8 | } 9 | 10 | const arrayContent = match[1]; 11 | 12 | const values = arrayContent.split(',').map((rawValue) => { 13 | let value = rawValue.trim().replace(/::\w+$/, ''); // Remove type casts like ::text, ::int 14 | 15 | // If it's a quoted string, clean and re-wrap it in single quotes 16 | if (/^'.*'$/.test(value)) { 17 | value = value.replace(/^'(.*)'$/, '$1'); 18 | return `'${value}'`; 19 | } 20 | 21 | // Otherwise assume it's a number or raw literal — return as-is 22 | return value; 23 | }); 24 | 25 | return `enum(${values.join(',')})`; 26 | } 27 | -------------------------------------------------------------------------------- /src/postgres-query-analyzer/parser.ts: -------------------------------------------------------------------------------- 1 | import { parseSql as _parseSql } from '@wsporto/typesql-parser/postgres'; 2 | import { defaultOptions, PostgresTraverseResult, traverseSmt } from './traverse'; 3 | import { PostgresColumnSchema } from '../drivers/types'; 4 | import { Result, err, ok } from 'neverthrow'; 5 | import { CheckConstraintResult } from '../drivers/postgres'; 6 | 7 | export function parseSql(sql: string, dbSchema: PostgresColumnSchema[], checkConstraints: CheckConstraintResult, options = defaultOptions()): PostgresTraverseResult { 8 | const parser = _parseSql(sql); 9 | 10 | const traverseResult = traverseSmt(parser.stmt(), dbSchema, checkConstraints, options); 11 | 12 | return traverseResult; 13 | } 14 | 15 | export function safeParseSql(sql: string, dbSchema: PostgresColumnSchema[], checkConstraints: CheckConstraintResult, options = defaultOptions()): Result { 16 | try { 17 | const result = parseSql(sql, dbSchema, checkConstraints, options); 18 | return ok(result); 19 | } 20 | catch (e) { 21 | const error = e as Error; 22 | return err(error.message); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/sqlite-query-analyzer/enum-parser.ts: -------------------------------------------------------------------------------- 1 | import { Column_defContext, Create_table_stmtContext, ExprContext, parseSql, Sql_stmtContext } from '@wsporto/typesql-parser/sqlite'; 2 | import { EnumColumnMap, EnumMap, EnumType } from './types'; 3 | 4 | export function enumParser(createStmts: string): EnumMap { 5 | const result = parseSql(createStmts); 6 | const enumMap: EnumMap = {}; 7 | result.sql_stmt_list().children?.forEach(stmt => { 8 | if (stmt instanceof Sql_stmtContext) { 9 | const create_table_stmt = stmt.create_table_stmt(); 10 | if (create_table_stmt) { 11 | collect_enum_create_table_stmt(create_table_stmt, enumMap); 12 | } 13 | } 14 | 15 | }); 16 | return enumMap; 17 | } 18 | 19 | function collect_enum_create_table_stmt(create_table_stmt: Create_table_stmtContext, enumMap: EnumMap) { 20 | const table_name = create_table_stmt.table_name().getText(); 21 | const enumColumnMap: EnumColumnMap = {}; 22 | create_table_stmt.column_def_list().forEach(column_def => { 23 | const column_name = column_def.column_name().getText(); 24 | const enum_column = enum_column_def(column_def); 25 | if (enum_column) { 26 | enumColumnMap[column_name] = enum_column; 27 | } 28 | }); 29 | enumMap[table_name] = enumColumnMap; 30 | 31 | } 32 | 33 | function enum_column_def(column_def: Column_defContext): EnumType | null { 34 | for (const column_constraint of column_def.column_constraint_list()) { 35 | if (column_constraint.CHECK_() && column_constraint.expr()) { 36 | return enum_column(column_constraint.expr()); 37 | } 38 | } 39 | return null; 40 | } 41 | 42 | function enum_column(expr: ExprContext): EnumType | null { 43 | if (expr.IN_()) { 44 | // expr IN expr 45 | const expr_list = expr.expr_list()[1].expr_list(); 46 | if (expr_list.length > 0) { 47 | const isEnum = expr_list.every(inExpr => isStringLiteral(inExpr)); 48 | if (isEnum) { 49 | return `ENUM(${expr_list.map(exprValue => exprValue.literal_value().getText()).join(',')})` 50 | } 51 | } 52 | return null 53 | } 54 | return null; 55 | } 56 | 57 | function isStringLiteral(expr: ExprContext) { 58 | const literal_value = expr.literal_value(); 59 | if (literal_value) { 60 | const string_literal = literal_value.STRING_LITERAL(); 61 | if (string_literal) { 62 | return true; 63 | } 64 | } 65 | return false; 66 | } 67 | -------------------------------------------------------------------------------- /src/sqlite-query-analyzer/replace-list-params.ts: -------------------------------------------------------------------------------- 1 | import { mapColumnType, postgresTypes } from '../dialects/postgres'; 2 | import type { ParameterNameAndPosition } from '../types'; 3 | 4 | export function replaceListParams(sql: string, listParamPositions: ParameterNameAndPosition[]): string { 5 | if (listParamPositions.length === 0) { 6 | return sql; 7 | } 8 | let newSql = ''; 9 | let start = 0; 10 | listParamPositions.forEach((param, index, array) => { 11 | newSql += sql.substring(start, param.paramPosition); 12 | newSql += `\${params.${param.name}.map(() => '?')}`; 13 | if (index === array.length - 1) { 14 | //last 15 | newSql += sql.substring(param.paramPosition + 1, sql.length); 16 | } 17 | start = param.paramPosition + 1; 18 | }); 19 | return newSql; 20 | } 21 | 22 | export function replacePostgresParams(sql: string, paramsIndexes: boolean[], paramsNames: string[]) { 23 | const paramRegex = /\$(\d+)/g; 24 | 25 | const newSql = sql.replace(paramRegex, (match, index) => { 26 | const paramIndex = parseInt(index, 10) - 1; // Adjust to zero-based index 27 | 28 | if (paramsIndexes[paramIndex]) { 29 | return `\${generatePlaceholders('${match}', params.${paramsNames[paramIndex]})}`; 30 | } else { 31 | return match; 32 | } 33 | }); 34 | return newSql; 35 | } 36 | 37 | export function replacePostgresParamsWithValues(sql: string, paramsIsList: boolean[], paramsTypes: number[]) { 38 | const paramRegex = /\$(\d+)/g; 39 | 40 | const newSql = sql.replace(paramRegex, (match, index) => { 41 | const paramIndex = parseInt(index, 10) - 1; 42 | return getValueForType(paramIndex, paramsTypes[paramIndex], paramsIsList[paramIndex]); 43 | }); 44 | return newSql; 45 | } 46 | 47 | function getValueForType(paramIndex: number, typeOid: number, isList: boolean): string { 48 | const type = postgresTypes[typeOid]; 49 | const tsType = mapColumnType(type); 50 | switch (tsType) { 51 | case 'number': 52 | if (isList) { 53 | return '1, 2'; 54 | } 55 | return `${paramIndex + 1}`; 56 | case 'number[]': 57 | return 'ARRAY[1, 2]' 58 | case 'string': 59 | if (isList) { 60 | return `'1', '2'`; 61 | } 62 | return `'${paramIndex + 1}'`; 63 | case 'string[]': 64 | return `ARRAY['1', '2']`; 65 | case 'Date': 66 | const date1 = new Date(); 67 | const date2 = new Date(); 68 | date2.setDate(date2.getDate() + 1); 69 | if (isList) { 70 | return `'${formatDate(date1)}', '${formatDate(date2)}'`; 71 | } 72 | return `'${formatDate(new Date())}'`; 73 | case 'Date[]': 74 | const date3 = new Date(); 75 | const date4 = new Date(); 76 | date4.setDate(date4.getDate() + 1); 77 | return `ARRAY['${formatDate(date3)}', '${formatDate(date4)}']`; 78 | case 'boolean': 79 | if (isList) { 80 | return 'true, true'; 81 | } 82 | return 'true'; 83 | case 'boolean[]': 84 | return 'ARRAY[true, true]'; 85 | } 86 | return '1'; 87 | } 88 | 89 | function formatDate(date: Date) { 90 | const year = date.getFullYear(); 91 | const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-indexed, so add 1 92 | const day = String(date.getDate()).padStart(2, '0'); 93 | 94 | return `${year}-${month}-${day}`; 95 | } 96 | -------------------------------------------------------------------------------- /src/sqlite-query-analyzer/types.ts: -------------------------------------------------------------------------------- 1 | export type EnumMap = { 2 | [table: string]: EnumColumnMap; 3 | } 4 | 5 | export type EnumColumnMap = { 6 | [columnName: string]: EnumType; 7 | } 8 | 9 | export type EnumType = `ENUM(${string})`; 10 | 11 | export type SQLiteType = 12 | | 'INTEGER' 13 | | 'INTEGER[]' 14 | | 'TEXT' 15 | | 'TEXT[]' 16 | | 'NUMERIC' 17 | | 'NUMERIC[]' 18 | | 'REAL' 19 | | 'REAL[]' 20 | | 'DATE' 21 | | 'DATE_TIME' 22 | | 'BLOB' 23 | | 'BOOLEAN' 24 | | EnumType 25 | | 'any'; 26 | 27 | export type PostgresType = 28 | | 'bool' 29 | | '_bool' 30 | | 'bool[]' 31 | | 'bytea' 32 | | 'bytea[]' 33 | | '_bytea' 34 | | 'char' 35 | | '_char' 36 | | 'char[]' 37 | | 'bpchar' 38 | | '_bpchar' 39 | | 'bpchar[]' 40 | | 'name' 41 | | '_name' 42 | | 'name[]' 43 | | 'int4' 44 | | '_int4' 45 | | 'int4[]' 46 | | 'int8' 47 | | '_int8' 48 | | 'int8[]' 49 | | 'int2' 50 | | '_int2' 51 | | 'int2[]' 52 | | 'text' 53 | | '_text' 54 | | 'text[]' 55 | | 'varchar' 56 | | '_varchar' 57 | | 'varchar[]' 58 | | 'date' 59 | | '_date' 60 | | 'date[]' 61 | | 'bit' 62 | | '_bit' 63 | | 'bit[]' 64 | | 'numeric' 65 | | '_numeric' 66 | | 'numeric[]' 67 | | 'uuid' 68 | | '_uuid' 69 | | 'uuid[]' 70 | | 'float4' 71 | | '_float4' 72 | | 'float4[]' 73 | | 'float8' 74 | | '_float8' 75 | | 'float8[]' 76 | | 'timestamp' 77 | | '_timestamp' 78 | | 'timestamp[]' 79 | | 'timestamptz' 80 | | '_timestamptz' 81 | | 'timestamptz[]'; 82 | -------------------------------------------------------------------------------- /src/ts-dynamic-query-descriptor.ts: -------------------------------------------------------------------------------- 1 | import type { TsFieldDescriptor } from './types'; 2 | 3 | export function mapToDynamicSelectColumns(columns: TsFieldDescriptor[]): TsFieldDescriptor[] { 4 | return columns.map((column) => mapToSelectColumn(column)); 5 | } 6 | 7 | function mapToSelectColumn(r: TsFieldDescriptor): TsFieldDescriptor { 8 | return { 9 | name: r.name, 10 | tsType: 'boolean', 11 | notNull: false 12 | }; 13 | } 14 | 15 | export function mapToDynamicResultColumns(columns: TsFieldDescriptor[]): TsFieldDescriptor[] { 16 | return columns.map((column) => mapToResultColumn(column)); 17 | } 18 | 19 | function mapToResultColumn(r: TsFieldDescriptor): TsFieldDescriptor { 20 | return { 21 | name: r.name, 22 | tsType: r.tsType, 23 | notNull: false 24 | }; 25 | } 26 | 27 | export function mapToDynamicParams(columns: TsFieldDescriptor[]): TsFieldDescriptor[] { 28 | return columns.map((column) => mapToDynamicParam(column)); 29 | } 30 | 31 | function mapToDynamicParam(r: TsFieldDescriptor): TsFieldDescriptor { 32 | return { 33 | name: r.name, 34 | tsType: `${r.tsType} | null`, 35 | notNull: false 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | export const indexGroupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 2 | return array.reduce((map, value, index, array) => { 3 | const key = predicate(value, index, array); 4 | map.get(key)?.push(index) ?? map.set(key, [index]); 5 | return map; 6 | }, new Map()); 7 | }; 8 | 9 | export const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 10 | return array.reduce((map, value, index, array) => { 11 | const key = predicate(value, index, array); 12 | map.get(key)?.push(value) ?? map.set(key, [value]); 13 | return map; 14 | }, new Map()); 15 | } 16 | -------------------------------------------------------------------------------- /src/utility-types.ts: -------------------------------------------------------------------------------- 1 | export interface Branding { 2 | _type: BrandT; 3 | } 4 | export type Brand = T & Branding; 5 | -------------------------------------------------------------------------------- /tests/check-mysql-inference.ts: -------------------------------------------------------------------------------- 1 | import { createConnection } from 'mysql2/promise'; 2 | import { convertTypeCodeToMysqlType } from '../src/mysql-mapping'; 3 | 4 | // https://github.com/sidorares/node-mysql2/blob/master/lib/constants/types.js 5 | async function main() { 6 | const conn = await createConnection('mysql://root:password@localhost/mydb'); 7 | 8 | const allTypes = [ 9 | 'decimal_column', 10 | 'tinyint_column', 11 | 'smallint_column', 12 | 'int_column', 13 | 'float_column', 14 | 'double_column', 15 | 'timestamp_column', 16 | 'bigint_column', 17 | 'mediumint_column', 18 | 'date_column', 19 | 'time_column', 20 | 'datetime_column', 21 | 'year_column', 22 | 'varchar_column', 23 | 'bit_column', 24 | 'json_column', 25 | 'enum_column', 26 | 'set_column', 27 | 'tinyblob_column', 28 | 'mediumblob_column', 29 | 'longblob_column', 30 | 'blob_column', 31 | 'tinytext_column', 32 | 'mediumtext_column', 33 | 'longtext_column', 34 | 'text_column', 35 | 'varbinary_column', 36 | 'binary_column', 37 | 'char_column', 38 | 'geometry_column' 39 | ]; 40 | 41 | //https://stackoverflow.com/questions/43241174/javascript-generating-all-combinations-of-elements-in-a-single-array-in-pairs 42 | const combinations = allTypes.flatMap((v, i) => allTypes.slice(i + 1).map((w) => ({ first: v, second: w }))); 43 | const firstUnionColumns = combinations.map((c) => c.first + generateAlias(c)).join(', '); 44 | const secondUnionColumns = combinations.map((c) => c.second + generateAlias(c)).join(', '); 45 | 46 | const generateSql = ` 47 | SELECT ${firstUnionColumns} FROM all_types 48 | UNION 49 | SELECT ${secondUnionColumns} FROM all_types`; 50 | // console.log("generatedSql=", generateSql); 51 | 52 | const result: any = await conn.prepare(generateSql); 53 | const resultArray: [{ name: string; type: string }] = result.statement.columns.map( 54 | (col: { name: string; columnType: number; columnLength: number; flags: number }) => ({ 55 | name: col.name, 56 | type: convertTypeCodeToMysqlType(col.columnType, col.flags, col.columnLength) 57 | }) 58 | ); 59 | const resultHash = resultArray.reduce((map, obj) => { 60 | map[obj.name] = obj.type; 61 | return map; 62 | }, {} as any); 63 | console.log('resultHash=', JSON.stringify(resultHash, null, 2)); 64 | } 65 | 66 | function removeColumnFromName(nameWithColumn: string) { 67 | return nameWithColumn.split('_')[0]; 68 | } 69 | 70 | function generateAlias(column: { first: string; second: string }) { 71 | return ` AS ${removeColumnFromName(column.first)}_${removeColumnFromName(column.second)}`; 72 | } 73 | 74 | // main(); 75 | -------------------------------------------------------------------------------- /tests/ext/uuid.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wsporto/typesql/6043ed7404bef17d2365e9109e3341d76de45c8d/tests/ext/uuid.dll -------------------------------------------------------------------------------- /tests/load-schema.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import { createMysqlClientForTest, loadMysqlSchema } from '../src/queryExectutor'; 3 | import type { MySqlDialect } from '../src/types'; 4 | 5 | describe('load-schema', () => { 6 | let client!: MySqlDialect; 7 | before(async () => { 8 | client = await createMysqlClientForTest('mysql://root:password@localhost/mydb'); 9 | }); 10 | 11 | it('filter schema', async () => { 12 | const actual = await loadMysqlSchema(client.client, client.schema); 13 | if (actual.isErr()) { 14 | assert.fail(`Shouldn't return an error: ${actual.error.description}`); 15 | } 16 | const schemas = actual.value.map((s) => s.schema); 17 | const uniqueSchemas = [...new Set(schemas)]; 18 | assert.deepStrictEqual(uniqueSchemas, ['mydb']); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/mysql-query-analyzer/column-nullability-functions.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import { dbSchema } from './create-schema'; 3 | import { parseAndInferNotNull } from '../../src/mysql-query-analyzer/infer-column-nullability'; 4 | 5 | describe('column-nullability - functions', () => { 6 | it('SELECT id FROM mytable1', () => { 7 | const sql = 'SELECT NOW(), CURDATE(), CURTIME()'; 8 | const actual = parseAndInferNotNull(sql, dbSchema); 9 | 10 | const expected = [true, true, true]; 11 | 12 | assert.deepStrictEqual(actual, expected); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/nested-ts-descriptor.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import { describeNestedQuery } from '../src/describe-nested-query'; 3 | import { createMysqlClientForTest, loadMysqlSchema } from '../src/queryExectutor'; 4 | import { extractQueryInfo } from '../src/mysql-query-analyzer/parse'; 5 | import { type NestedTsDescriptor, createNestedTsDescriptor } from '../src/ts-nested-descriptor'; 6 | import type { MySqlDialect } from '../src/types'; 7 | 8 | describe('Test nested-ts-descriptor', () => { 9 | let client!: MySqlDialect; 10 | before(async () => { 11 | client = await createMysqlClientForTest('mysql://root:password@localhost/mydb'); 12 | }); 13 | 14 | it('SELECT FROM users u INNER JOIN posts p', async () => { 15 | const dbSchema = await loadMysqlSchema(client.client, client.schema); 16 | 17 | const sql = ` 18 | SELECT 19 | u.id as user_id, 20 | u.name as user_name, 21 | p.id as post_id, 22 | p.title as post_title 23 | FROM users u 24 | INNER JOIN posts p on p.fk_user = u.id 25 | `; 26 | 27 | if (dbSchema.isErr()) { 28 | assert.fail(`Shouldn't return an error: ${dbSchema.error.description}`); 29 | } 30 | const model = describeNestedQuery(sql, dbSchema.value); 31 | const queryInfo = extractQueryInfo(sql, dbSchema.value); 32 | assert(queryInfo.kind === 'Select'); 33 | const actual = createNestedTsDescriptor(queryInfo.columns, model); 34 | const expected: NestedTsDescriptor = { 35 | relations: [ 36 | { 37 | name: 'users', 38 | groupKeyIndex: 0, 39 | fields: [ 40 | { 41 | type: 'field', 42 | name: 'user_id', 43 | index: 0, 44 | tsType: 'number', 45 | notNull: true 46 | }, 47 | { 48 | type: 'field', 49 | name: 'user_name', 50 | index: 1, 51 | tsType: 'string', 52 | notNull: true 53 | }, 54 | { 55 | type: 'relation', 56 | name: 'posts', 57 | list: true, 58 | tsType: 'posts[]', 59 | notNull: true 60 | } 61 | ] 62 | }, 63 | { 64 | name: 'posts', 65 | groupKeyIndex: 2, 66 | fields: [ 67 | { 68 | type: 'field', 69 | name: 'post_id', 70 | index: 2, 71 | tsType: 'number', 72 | notNull: true 73 | }, 74 | { 75 | type: 'field', 76 | name: 'post_title', 77 | index: 3, 78 | tsType: 'string', 79 | notNull: true 80 | } 81 | ] 82 | } 83 | ] 84 | }; 85 | assert.deepStrictEqual(actual, expected); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/derivated-table.sql: -------------------------------------------------------------------------------- 1 | -- @dynamicQuery 2 | SELECT m1.id, m2.name 3 | FROM mytable1 m1 4 | INNER JOIN ( -- derivated table 5 | SELECT id, name from mytable2 m 6 | WHERE m.name = :subqueryName 7 | ) m2 on m2.id = m1.id -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/dynamic-query-01.sql: -------------------------------------------------------------------------------- 1 | -- @dynamicQuery 2 | SELECT m1.id, m1.value, m2.name, m2.descr as description 3 | FROM mytable1 m1 4 | INNER JOIN mytable2 m2 on m1.id = m2.id -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/dynamic-query03.sql: -------------------------------------------------------------------------------- 1 | -- @dynamicQuery 2 | SELECT t1.id, t1.value 3 | FROM mytable1 t1 -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/dynamic-query04.sql: -------------------------------------------------------------------------------- 1 | -- @dynamicQuery 2 | SELECT 3 | * 4 | FROM mytable1 m1 5 | INNER JOIN mytable2 m2 on m2.id = m1.id -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/dynamic-query05.sql: -------------------------------------------------------------------------------- 1 | -- @dynamicQuery 2 | WITH 3 | cte as ( 4 | select id, name from mytable2 5 | ) 6 | SELECT 7 | m1.id, 8 | m2.name 9 | FROM mytable1 m1 10 | INNER JOIN cte m2 on m2.id = m1.id -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/dynamic-query08.sql: -------------------------------------------------------------------------------- 1 | -- @dynamicQuery 2 | SELECT 3 | timestamp_not_null_column 4 | FROM all_types 5 | WHERE EXTRACT(YEAR FROM timestamp_not_null_column) = :param1 AND EXTRACT(MONTH FROM timestamp_not_null_column) = :param2 -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./derivated-table"; 2 | export * from "./dynamic-query-01"; 3 | export * from "./dynamic-query03"; 4 | export * from "./dynamic-query04"; 5 | export * from "./dynamic-query05"; 6 | export * from "./dynamic-query08"; 7 | export * from "./nested01"; 8 | export * from "./nested02"; 9 | export * from "./nested03"; 10 | export * from "./nested04-without-join-table"; 11 | export * from "./nested04"; 12 | -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/nested01.sql: -------------------------------------------------------------------------------- 1 | -- @nested 2 | SELECT 3 | u.id as user_id, 4 | u.name as user_name, 5 | p.id as post_id, 6 | p.title as post_title 7 | FROM users u 8 | INNER JOIN posts p on p.fk_user = u.id -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/nested01.ts: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Nested01Result = { 4 | user_id: number; 5 | user_name: string; 6 | post_id: number; 7 | post_title: string; 8 | } 9 | 10 | export type Nested01NestedUsers = { 11 | user_id: number; 12 | user_name: string; 13 | posts: Nested01NestedPosts[]; 14 | } 15 | 16 | export type Nested01NestedPosts = { 17 | post_id: number; 18 | post_title: string; 19 | } 20 | 21 | export async function nested01(client: pg.Client | pg.Pool): Promise { 22 | const sql = ` 23 | -- @nested 24 | SELECT 25 | u.id as user_id, 26 | u.name as user_name, 27 | p.id as post_id, 28 | p.title as post_title 29 | FROM users u 30 | INNER JOIN posts p on p.fk_user = u.id 31 | ` 32 | return client.query({ text: sql, rowMode: 'array' }) 33 | .then(res => res.rows.map(row => mapArrayToNested01Result(row))); 34 | } 35 | 36 | function mapArrayToNested01Result(data: any) { 37 | const result: Nested01Result = { 38 | user_id: data[0], 39 | user_name: data[1], 40 | post_id: data[2], 41 | post_title: data[3] 42 | } 43 | return result; 44 | } 45 | 46 | export async function nested01Nested(client: pg.Client | pg.Pool): Promise { 47 | const selectResult = await nested01(client); 48 | if (selectResult.length == 0) { 49 | return []; 50 | } 51 | return collectNested01NestedUsers(selectResult); 52 | } 53 | 54 | function collectNested01NestedUsers(selectResult: Nested01Result[]): Nested01NestedUsers[] { 55 | const grouped = groupBy(selectResult.filter(r => r.user_id != null), r => r.user_id); 56 | return [...grouped.values()].map(row => ({ 57 | user_id: row[0].user_id!, 58 | user_name: row[0].user_name!, 59 | posts: collectNested01NestedPosts(row), 60 | })) 61 | } 62 | 63 | function collectNested01NestedPosts(selectResult: Nested01Result[]): Nested01NestedPosts[] { 64 | const grouped = groupBy(selectResult.filter(r => r.post_id != null), r => r.post_id); 65 | return [...grouped.values()].map(row => ({ 66 | post_id: row[0].post_id!, 67 | post_title: row[0].post_title!, 68 | })) 69 | } 70 | 71 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 72 | return array.reduce((map, value, index, array) => { 73 | const key = predicate(value, index, array); 74 | map.get(key)?.push(value) ?? map.set(key, [value]); 75 | return map; 76 | }, new Map()); 77 | } -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/nested02.sql: -------------------------------------------------------------------------------- 1 | -- @nested 2 | SELECT 3 | u.id as user_id, 4 | u.name as user_name, 5 | p.id as post_id, 6 | p.title as post_title, 7 | p.body as post_body, 8 | r.id as role_id, 9 | r.role, 10 | c.id as comment_id, 11 | c.comment 12 | FROM users u 13 | INNER JOIN posts p on p.fk_user = u.id 14 | INNER JOIN roles r on r.fk_user = u.id 15 | INNER JOIN comments c on c.fk_post = p.id 16 | ORDER BY user_id, post_id, role_id, comment_id -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/nested03.sql: -------------------------------------------------------------------------------- 1 | -- @nested 2 | SELECT 3 | c.id, 4 | a1.*, 5 | a2.* 6 | FROM clients as c 7 | INNER JOIN addresses as a1 ON a1.id = c.primaryAddress 8 | LEFT JOIN addresses as a2 ON a2.id = c.secondaryAddress 9 | WHERE c.id = $1 -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/nested03.ts: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Nested03Params = { 4 | param1: number; 5 | } 6 | 7 | export type Nested03Result = { 8 | id: number; 9 | id_2: number; 10 | address: string; 11 | id_3?: number; 12 | address_2?: string; 13 | } 14 | 15 | export type Nested03NestedC = { 16 | id: number; 17 | a1: Nested03NestedA1; 18 | a2?: Nested03NestedA2; 19 | } 20 | 21 | export type Nested03NestedA1 = { 22 | id: number; 23 | address: string; 24 | } 25 | 26 | export type Nested03NestedA2 = { 27 | id: number | null; 28 | address: string | null; 29 | } 30 | 31 | export async function nested03(client: pg.Client | pg.Pool, params: Nested03Params): Promise { 32 | const sql = ` 33 | -- @nested 34 | SELECT 35 | c.id, 36 | a1.*, 37 | a2.* 38 | FROM clients as c 39 | INNER JOIN addresses as a1 ON a1.id = c.primaryAddress 40 | LEFT JOIN addresses as a2 ON a2.id = c.secondaryAddress 41 | WHERE c.id = $1 42 | ` 43 | return client.query({ text: sql, rowMode: 'array', values: [params.param1] }) 44 | .then(res => res.rows.map(row => mapArrayToNested03Result(row))); 45 | } 46 | 47 | function mapArrayToNested03Result(data: any) { 48 | const result: Nested03Result = { 49 | id: data[0], 50 | id_2: data[1], 51 | address: data[2], 52 | id_3: data[3], 53 | address_2: data[4] 54 | } 55 | return result; 56 | } 57 | 58 | export async function nested03Nested(client: pg.Client | pg.Pool, params: Nested03Params): Promise { 59 | const selectResult = await nested03(client, params); 60 | if (selectResult.length == 0) { 61 | return []; 62 | } 63 | return collectNested03NestedC(selectResult); 64 | } 65 | 66 | function collectNested03NestedC(selectResult: Nested03Result[]): Nested03NestedC[] { 67 | const grouped = groupBy(selectResult.filter(r => r.id != null), r => r.id); 68 | return [...grouped.values()].map(row => ({ 69 | id: row[0].id!, 70 | a1: collectNested03NestedA1(row)[0], 71 | a2: collectNested03NestedA2(row)[0], 72 | })) 73 | } 74 | 75 | function collectNested03NestedA1(selectResult: Nested03Result[]): Nested03NestedA1[] { 76 | const grouped = groupBy(selectResult.filter(r => r.id_2 != null), r => r.id_2); 77 | return [...grouped.values()].map(row => ({ 78 | id: row[0].id_2!, 79 | address: row[0].address!, 80 | })) 81 | } 82 | 83 | function collectNested03NestedA2(selectResult: Nested03Result[]): Nested03NestedA2[] { 84 | const grouped = groupBy(selectResult.filter(r => r.id_3 != null), r => r.id_3); 85 | return [...grouped.values()].map(row => ({ 86 | id: row[0].id_3!, 87 | address: row[0].address_2!, 88 | })) 89 | } 90 | 91 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 92 | return array.reduce((map, value, index, array) => { 93 | const key = predicate(value, index, array); 94 | map.get(key)?.push(value) ?? map.set(key, [value]); 95 | return map; 96 | }, new Map()); 97 | } -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/nested04-without-join-table.sql: -------------------------------------------------------------------------------- 1 | -- @nested 2 | SELECT 3 | s.*, 4 | u.* 5 | FROM surveys s 6 | INNER JOIN participants p on p.fk_survey = s.id 7 | INNER JOIN users u on u.id = p.fk_user -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/nested04-without-join-table.ts: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Nested04WithoutJoinTableResult = { 4 | id: number; 5 | name: string; 6 | id_2: number; 7 | name_2: string; 8 | email?: string; 9 | } 10 | 11 | export type Nested04WithoutJoinTableNestedSurveys = { 12 | id: number; 13 | name: string; 14 | users: Nested04WithoutJoinTableNestedUsers[]; 15 | } 16 | 17 | export type Nested04WithoutJoinTableNestedUsers = { 18 | id: number; 19 | name: string; 20 | email: string | null; 21 | } 22 | 23 | export async function nested04WithoutJoinTable(client: pg.Client | pg.Pool): Promise { 24 | const sql = ` 25 | -- @nested 26 | SELECT 27 | s.*, 28 | u.* 29 | FROM surveys s 30 | INNER JOIN participants p on p.fk_survey = s.id 31 | INNER JOIN users u on u.id = p.fk_user 32 | ` 33 | return client.query({ text: sql, rowMode: 'array' }) 34 | .then(res => res.rows.map(row => mapArrayToNested04WithoutJoinTableResult(row))); 35 | } 36 | 37 | function mapArrayToNested04WithoutJoinTableResult(data: any) { 38 | const result: Nested04WithoutJoinTableResult = { 39 | id: data[0], 40 | name: data[1], 41 | id_2: data[2], 42 | name_2: data[3], 43 | email: data[4] 44 | } 45 | return result; 46 | } 47 | 48 | export async function nested04WithoutJoinTableNested(client: pg.Client | pg.Pool): Promise { 49 | const selectResult = await nested04WithoutJoinTable(client); 50 | if (selectResult.length == 0) { 51 | return []; 52 | } 53 | return collectNested04WithoutJoinTableNestedSurveys(selectResult); 54 | } 55 | 56 | function collectNested04WithoutJoinTableNestedSurveys(selectResult: Nested04WithoutJoinTableResult[]): Nested04WithoutJoinTableNestedSurveys[] { 57 | const grouped = groupBy(selectResult.filter(r => r.id != null), r => r.id); 58 | return [...grouped.values()].map(row => ({ 59 | id: row[0].id!, 60 | name: row[0].name!, 61 | users: collectNested04WithoutJoinTableNestedUsers(row), 62 | })) 63 | } 64 | 65 | function collectNested04WithoutJoinTableNestedUsers(selectResult: Nested04WithoutJoinTableResult[]): Nested04WithoutJoinTableNestedUsers[] { 66 | const grouped = groupBy(selectResult.filter(r => r.id_2 != null), r => r.id_2); 67 | return [...grouped.values()].map(row => ({ 68 | id: row[0].id_2!, 69 | name: row[0].name_2!, 70 | email: row[0].email!, 71 | })) 72 | } 73 | 74 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 75 | return array.reduce((map, value, index, array) => { 76 | const key = predicate(value, index, array); 77 | map.get(key)?.push(value) ?? map.set(key, [value]); 78 | return map; 79 | }, new Map()); 80 | } -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/nested04.sql: -------------------------------------------------------------------------------- 1 | -- @nested 2 | SELECT 3 | s.id as surveyId, 4 | s.name as surveyName, 5 | p.id as participantId, 6 | u.id as userId, 7 | u.name as userName 8 | FROM surveys s 9 | INNER JOIN participants p on p.fk_survey = s.id 10 | INNER JOIN users u on u.id = p.fk_user -------------------------------------------------------------------------------- /tests/postgres-e2e/sql/nested04.ts: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Nested04Result = { 4 | surveyid: number; 5 | surveyname: string; 6 | participantid: number; 7 | userid: number; 8 | username: string; 9 | } 10 | 11 | export type Nested04NestedSurveys = { 12 | surveyid: number; 13 | surveyname: string; 14 | participants: Nested04NestedParticipants[]; 15 | } 16 | 17 | export type Nested04NestedParticipants = { 18 | participantid: number; 19 | users: Nested04NestedUsers; 20 | } 21 | 22 | export type Nested04NestedUsers = { 23 | userid: number; 24 | username: string; 25 | } 26 | 27 | export async function nested04(client: pg.Client | pg.Pool): Promise { 28 | const sql = ` 29 | -- @nested 30 | SELECT 31 | s.id as surveyId, 32 | s.name as surveyName, 33 | p.id as participantId, 34 | u.id as userId, 35 | u.name as userName 36 | FROM surveys s 37 | INNER JOIN participants p on p.fk_survey = s.id 38 | INNER JOIN users u on u.id = p.fk_user 39 | ` 40 | return client.query({ text: sql, rowMode: 'array' }) 41 | .then(res => res.rows.map(row => mapArrayToNested04Result(row))); 42 | } 43 | 44 | function mapArrayToNested04Result(data: any) { 45 | const result: Nested04Result = { 46 | surveyid: data[0], 47 | surveyname: data[1], 48 | participantid: data[2], 49 | userid: data[3], 50 | username: data[4] 51 | } 52 | return result; 53 | } 54 | 55 | export async function nested04Nested(client: pg.Client | pg.Pool): Promise { 56 | const selectResult = await nested04(client); 57 | if (selectResult.length == 0) { 58 | return []; 59 | } 60 | return collectNested04NestedSurveys(selectResult); 61 | } 62 | 63 | function collectNested04NestedSurveys(selectResult: Nested04Result[]): Nested04NestedSurveys[] { 64 | const grouped = groupBy(selectResult.filter(r => r.surveyid != null), r => r.surveyid); 65 | return [...grouped.values()].map(row => ({ 66 | surveyid: row[0].surveyid!, 67 | surveyname: row[0].surveyname!, 68 | participants: collectNested04NestedParticipants(row), 69 | })) 70 | } 71 | 72 | function collectNested04NestedParticipants(selectResult: Nested04Result[]): Nested04NestedParticipants[] { 73 | const grouped = groupBy(selectResult.filter(r => r.participantid != null), r => r.participantid); 74 | return [...grouped.values()].map(row => ({ 75 | participantid: row[0].participantid!, 76 | users: collectNested04NestedUsers(row)[0], 77 | })) 78 | } 79 | 80 | function collectNested04NestedUsers(selectResult: Nested04Result[]): Nested04NestedUsers[] { 81 | const grouped = groupBy(selectResult.filter(r => r.userid != null), r => r.userid); 82 | return [...grouped.values()].map(row => ({ 83 | userid: row[0].userid!, 84 | username: row[0].username!, 85 | })) 86 | } 87 | 88 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 89 | return array.reduce((map, value, index, array) => { 90 | const key = predicate(value, index, array); 91 | map.get(key)?.push(value) ?? map.set(key, [value]); 92 | return map; 93 | }, new Map()); 94 | } -------------------------------------------------------------------------------- /tests/postgres-e2e/typesql.json: -------------------------------------------------------------------------------- 1 | { 2 | "databaseUri": "postgres://postgres:password@127.0.0.1:5432/postgres", 3 | "sqlDir": "./tests/postgres-e2e/sql", 4 | "client": "pg" 5 | } -------------------------------------------------------------------------------- /tests/postgres/describe-copy-stmt.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import type { SchemaDef } from '../../src/types'; 3 | import postgres from 'postgres'; 4 | import { describeQuery } from '../../src/postgres-query-analyzer/describe'; 5 | 6 | describe('postgres-describe-copy-stmt', () => { 7 | const postres = postgres({ 8 | host: 'localhost', 9 | username: 'postgres', 10 | password: 'password', 11 | database: 'postgres', 12 | port: 5432 13 | }); 14 | 15 | it('COPY mytable1 (value) FROM STDIN WITH CSV', async () => { 16 | const sql = 'COPY mytable1 (value) FROM STDIN WITH CSV'; 17 | const actual = await describeQuery(postres, sql, []); 18 | const expected: SchemaDef = { 19 | multipleRowsResult: false, 20 | queryType: 'Copy', 21 | sql, 22 | columns: [], 23 | parameters: [ 24 | { 25 | name: 'value', 26 | columnType: 'int4', 27 | notNull: false 28 | } 29 | ] 30 | }; 31 | 32 | if (actual.isErr()) { 33 | assert.fail(`Shouldn't return an error: ${actual.error.description}`); 34 | } 35 | assert.deepStrictEqual(actual.value, expected); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /tests/postgres/enum-parser.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import { transformCheckToEnum } from '../../src/postgres-query-analyzer/enum-parser'; 3 | 4 | describe('postgres-enum-parser', () => { 5 | 6 | it('enum-parser', () => { 7 | const checkConstraint = `CHECK ((enum_constraint = ANY (ARRAY['x-small'::text, 'small'::text, 'medium'::text, 'large'::text, 'x-large'::text])))`; 8 | const actual = transformCheckToEnum(checkConstraint); 9 | const expected = `enum('x-small','small','medium','large','x-large')`; 10 | assert.deepStrictEqual(actual, expected); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/postgres/expected-code/copy01.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | import { from as copyFrom } from 'pg-copy-streams'; 3 | import { pipeline } from 'stream/promises'; 4 | import { Readable } from 'stream'; 5 | 6 | export type Copy01Params = { 7 | value: number | null; 8 | } 9 | 10 | export async function copy01(client: pg.Client | pg.PoolClient, values: Copy01Params[]): Promise { 11 | const sql = ` 12 | COPY mytable1 (value) FROM STDIN WITH CSV 13 | ` 14 | const csv = jsonToCsv(values); 15 | 16 | const sourceStream = Readable.from(csv); 17 | const stream = client.query(copyFrom(sql)); 18 | await pipeline(sourceStream, stream) 19 | } 20 | 21 | function jsonToCsv(values: Copy01Params[]): string { 22 | return values 23 | .map(value => 24 | Object.values(value) 25 | .map(val => escapeValue(val)) 26 | .join(',') 27 | ) 28 | .join('\n'); 29 | } 30 | 31 | function escapeValue(val: any): string { 32 | return val != null ? JSON.stringify(val).replace(/\n/g, '\\n') : ''; 33 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/crud-delete01.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type DeleteFromMytable1Params = { 4 | id: number; 5 | } 6 | 7 | export type DeleteFromMytable1Result = { 8 | id: number; 9 | value?: number; 10 | } 11 | 12 | export async function deleteFromMytable1(client: pg.Client | pg.Pool, params: DeleteFromMytable1Params): Promise { 13 | const sql = ` 14 | DELETE FROM mytable1 WHERE id = $1 15 | ` 16 | return client.query({ text: sql, rowMode: 'array', values: [params.id] }) 17 | .then(res => res.rows.length > 0 ? mapArrayToDeleteFromMytable1Result(res.rows[0]) : null); 18 | } 19 | 20 | function mapArrayToDeleteFromMytable1Result(data: any) { 21 | const result: DeleteFromMytable1Result = { 22 | id: data[0], 23 | value: data[1] 24 | } 25 | return result; 26 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/crud-insert01.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type InsertIntoMytable1Params = { 4 | value: number | null; 5 | } 6 | 7 | export type InsertIntoMytable1Result = { 8 | id: number; 9 | value?: number; 10 | } 11 | 12 | export async function insertIntoMytable1(client: pg.Client | pg.Pool, params: InsertIntoMytable1Params): Promise { 13 | const sql = ` 14 | INSERT INTO mytable1 (value) 15 | VALUES ($1) 16 | RETURNING * 17 | ` 18 | return client.query({ text: sql, values: [params.value] }) 19 | .then(res => mapArrayToInsertIntoMytable1Result(res)); 20 | } 21 | 22 | function mapArrayToInsertIntoMytable1Result(data: any) { 23 | const result: InsertIntoMytable1Result = { 24 | id: data[0], 25 | value: data[1] 26 | } 27 | return result; 28 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/crud-select01.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type SelectFromMytable1Params = { 4 | id: number; 5 | } 6 | 7 | export type SelectFromMytable1Result = { 8 | id: number; 9 | value?: number; 10 | } 11 | 12 | export async function selectFromMytable1(client: pg.Client | pg.Pool, params: SelectFromMytable1Params): Promise { 13 | const sql = ` 14 | SELECT 15 | id, 16 | value 17 | FROM mytable1 18 | WHERE id = $1 19 | ` 20 | return client.query({ text: sql, rowMode: 'array', values: [params.id] }) 21 | .then(res => res.rows.length > 0 ? mapArrayToSelectFromMytable1Result(res.rows[0]) : null); 22 | } 23 | 24 | function mapArrayToSelectFromMytable1Result(data: any) { 25 | const result: SelectFromMytable1Result = { 26 | id: data[0], 27 | value: data[1] 28 | } 29 | return result; 30 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/crud-update01.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type UpdateMytable1Data = { 4 | value: number | null; 5 | } 6 | 7 | export type UpdateMytable1Params = { 8 | id: number; 9 | } 10 | 11 | export type UpdateMytable1Result = { 12 | id: number; 13 | value?: number; 14 | } 15 | 16 | export async function updateMytable1(client: pg.Client | pg.Pool, data: UpdateMytable1Data, params: UpdateMytable1Params): Promise { 17 | let sql = 'UPDATE mytable1 SET'; 18 | const values: any[] = []; 19 | if (data.value !== undefined) { 20 | sql += ' value = $1'; 21 | values.push(data.value); 22 | } 23 | sql += ' WHERE id = $2 RETURNING *'; 24 | values.push(params.id); 25 | if (values.length > 0) { 26 | return client.query({ text: sql, values }) 27 | .then(res => res.rows.length > 0 ? mapArrayToUpdateMytable1Result(res) : null); 28 | } 29 | return null; 30 | } 31 | 32 | function mapArrayToUpdateMytable1Result(data: any) { 33 | const result: UpdateMytable1Result = { 34 | id: data[0], 35 | value: data[1] 36 | } 37 | return result; 38 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/crud-update02.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type UpdateMytable2Data = { 4 | name: string | null; 5 | descr: string | null; 6 | } 7 | 8 | export type UpdateMytable2Params = { 9 | id: number; 10 | } 11 | 12 | export type UpdateMytable2Result = { 13 | id: number; 14 | name?: string; 15 | descr?: string; 16 | } 17 | 18 | export async function updateMytable2(client: pg.Client | pg.Pool, data: UpdateMytable2Data, params: UpdateMytable2Params): Promise { 19 | let sql = 'UPDATE mytable2 SET'; 20 | const values: any[] = []; 21 | if (data.name !== undefined) { 22 | sql += ' name = $1'; 23 | values.push(data.name); 24 | } 25 | if (data.descr !== undefined) { 26 | if (values.length > 0) sql += ','; 27 | sql += ' descr = $2'; 28 | values.push(data.descr); 29 | } 30 | sql += ' WHERE id = $3 RETURNING *'; 31 | values.push(params.id); 32 | if (values.length > 0) { 33 | return client.query({ text: sql, values }) 34 | .then(res => res.rows.length > 0 ? mapArrayToUpdateMytable2Result(res) : null); 35 | } 36 | return null; 37 | } 38 | 39 | function mapArrayToUpdateMytable2Result(data: any) { 40 | const result: UpdateMytable2Result = { 41 | id: data[0], 42 | name: data[1], 43 | descr: data[2] 44 | } 45 | return result; 46 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/delete01.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Delete01Params = { 4 | param1: number; 5 | } 6 | 7 | export type Delete01Result = { 8 | rowCount: number; 9 | } 10 | 11 | export async function delete01(client: pg.Client | pg.Pool, params: Delete01Params): Promise { 12 | const sql = ` 13 | DELETE FROM mytable1 WHERE id=$1 14 | ` 15 | return client.query({ text: sql, values: [params.param1] }) 16 | .then(res => mapArrayToDelete01Result(res)); 17 | } 18 | 19 | function mapArrayToDelete01Result(data: any) { 20 | const result: Delete01Result = { 21 | rowCount: data.rowCount 22 | } 23 | return result; 24 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/insert01.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Insert01Result = { 4 | rowCount: number; 5 | } 6 | 7 | export async function insert01(client: pg.Client | pg.Pool): Promise { 8 | const sql = ` 9 | INSERT INTO mytable1(value) values(10) 10 | ` 11 | return client.query({ text: sql }) 12 | .then(res => mapArrayToInsert01Result(res)); 13 | } 14 | 15 | function mapArrayToInsert01Result(data: any) { 16 | const result: Insert01Result = { 17 | rowCount: data.rowCount 18 | } 19 | return result; 20 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/insert02.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Insert02Params = { 4 | param1: number | null; 5 | } 6 | 7 | export type Insert02Result = { 8 | rowCount: number; 9 | } 10 | 11 | export async function insert02(client: pg.Client | pg.Pool, params: Insert02Params): Promise { 12 | const sql = ` 13 | INSERT INTO mytable1(value) values($1) 14 | ` 15 | return client.query({ text: sql, values: [params.param1] }) 16 | .then(res => mapArrayToInsert02Result(res)); 17 | } 18 | 19 | function mapArrayToInsert02Result(data: any) { 20 | const result: Insert02Result = { 21 | rowCount: data.rowCount 22 | } 23 | return result; 24 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/insert03.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Insert03Params = { 4 | value: number | null; 5 | } 6 | 7 | export type Insert03Result = { 8 | id: number; 9 | value?: number; 10 | } 11 | 12 | export async function insert03(client: pg.Client | pg.Pool, params: Insert03Params): Promise { 13 | const sql = ` 14 | INSERT INTO mytable1(value) VALUES($1) RETURNING * 15 | ` 16 | return client.query({ text: sql, rowMode: 'array', values: [params.value] }) 17 | .then(res => mapArrayToInsert03Result(res.rows[0])); 18 | } 19 | 20 | function mapArrayToInsert03Result(data: any) { 21 | const result: Insert03Result = { 22 | id: data[0], 23 | value: data[1] 24 | } 25 | return result; 26 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/insert04-default.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Insert04Params = { 4 | value: number | null; 5 | } 6 | 7 | export type Insert04Result = { 8 | rowCount: number; 9 | } 10 | 11 | export async function insert04(client: pg.Client | pg.Pool, params: Insert04Params): Promise { 12 | const sql = ` 13 | INSERT INTO all_types(integer_column_default) VALUES ($1) 14 | ` 15 | return client.query({ text: sql, values: [params.value] }) 16 | .then(res => mapArrayToInsert04Result(res)); 17 | } 18 | 19 | function mapArrayToInsert04Result(data: any) { 20 | const result: Insert04Result = { 21 | rowCount: data.rowCount 22 | } 23 | return result; 24 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/nested01.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Nested01Result = { 4 | user_id: number; 5 | user_name: string; 6 | post_id: number; 7 | post_title: string; 8 | } 9 | 10 | export type Nested01NestedUsers = { 11 | user_id: number; 12 | user_name: string; 13 | posts: Nested01NestedPosts[]; 14 | } 15 | 16 | export type Nested01NestedPosts = { 17 | post_id: number; 18 | post_title: string; 19 | } 20 | 21 | export async function nested01(client: pg.Client | pg.Pool): Promise { 22 | const sql = ` 23 | -- @nested 24 | SELECT 25 | u.id as user_id, 26 | u.name as user_name, 27 | p.id as post_id, 28 | p.title as post_title 29 | FROM users u 30 | INNER JOIN posts p on p.fk_user = u.id 31 | ` 32 | return client.query({ text: sql, rowMode: 'array' }) 33 | .then(res => res.rows.map(row => mapArrayToNested01Result(row))); 34 | } 35 | 36 | function mapArrayToNested01Result(data: any) { 37 | const result: Nested01Result = { 38 | user_id: data[0], 39 | user_name: data[1], 40 | post_id: data[2], 41 | post_title: data[3] 42 | } 43 | return result; 44 | } 45 | 46 | export async function nested01Nested(client: pg.Client | pg.Pool): Promise { 47 | const selectResult = await nested01(client); 48 | if (selectResult.length == 0) { 49 | return []; 50 | } 51 | return collectNested01NestedUsers(selectResult); 52 | } 53 | 54 | function collectNested01NestedUsers(selectResult: Nested01Result[]): Nested01NestedUsers[] { 55 | const grouped = groupBy(selectResult.filter(r => r.user_id != null), r => r.user_id); 56 | return [...grouped.values()].map(row => ({ 57 | user_id: row[0].user_id!, 58 | user_name: row[0].user_name!, 59 | posts: collectNested01NestedPosts(row), 60 | })) 61 | } 62 | 63 | function collectNested01NestedPosts(selectResult: Nested01Result[]): Nested01NestedPosts[] { 64 | const grouped = groupBy(selectResult.filter(r => r.post_id != null), r => r.post_id); 65 | return [...grouped.values()].map(row => ({ 66 | post_id: row[0].post_id!, 67 | post_title: row[0].post_title!, 68 | })) 69 | } 70 | 71 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 72 | return array.reduce((map, value, index, array) => { 73 | const key = predicate(value, index, array); 74 | map.get(key)?.push(value) ?? map.set(key, [value]); 75 | return map; 76 | }, new Map()); 77 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/nested02-clients-with-addresses.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Nested02Params = { 4 | clientId: number; 5 | } 6 | 7 | export type Nested02Result = { 8 | id: number; 9 | id_2: number; 10 | address: string; 11 | id_3?: number; 12 | address_2?: string; 13 | } 14 | 15 | export type Nested02NestedC = { 16 | id: number; 17 | a1: Nested02NestedA1; 18 | a2?: Nested02NestedA2; 19 | } 20 | 21 | export type Nested02NestedA1 = { 22 | id: number; 23 | address: string; 24 | } 25 | 26 | export type Nested02NestedA2 = { 27 | id: number | null; 28 | address: string | null; 29 | } 30 | 31 | export async function nested02(client: pg.Client | pg.Pool, params: Nested02Params): Promise { 32 | const sql = ` 33 | -- @nested 34 | SELECT 35 | c.id, 36 | a1.*, 37 | a2.* 38 | FROM clients as c 39 | INNER JOIN addresses as a1 ON a1.id = c.primaryAddress 40 | LEFT JOIN addresses as a2 ON a2.id = c.secondaryAddress 41 | WHERE c.id = $1 42 | ` 43 | return client.query({ text: sql, rowMode: 'array', values: [params.clientId] }) 44 | .then(res => res.rows.map(row => mapArrayToNested02Result(row))); 45 | } 46 | 47 | function mapArrayToNested02Result(data: any) { 48 | const result: Nested02Result = { 49 | id: data[0], 50 | id_2: data[1], 51 | address: data[2], 52 | id_3: data[3], 53 | address_2: data[4] 54 | } 55 | return result; 56 | } 57 | 58 | export async function nested02Nested(client: pg.Client | pg.Pool, params: Nested02Params): Promise { 59 | const selectResult = await nested02(client, params); 60 | if (selectResult.length == 0) { 61 | return []; 62 | } 63 | return collectNested02NestedC(selectResult); 64 | } 65 | 66 | function collectNested02NestedC(selectResult: Nested02Result[]): Nested02NestedC[] { 67 | const grouped = groupBy(selectResult.filter(r => r.id != null), r => r.id); 68 | return [...grouped.values()].map(row => ({ 69 | id: row[0].id!, 70 | a1: collectNested02NestedA1(row)[0], 71 | a2: collectNested02NestedA2(row)[0], 72 | })) 73 | } 74 | 75 | function collectNested02NestedA1(selectResult: Nested02Result[]): Nested02NestedA1[] { 76 | const grouped = groupBy(selectResult.filter(r => r.id_2 != null), r => r.id_2); 77 | return [...grouped.values()].map(row => ({ 78 | id: row[0].id_2!, 79 | address: row[0].address!, 80 | })) 81 | } 82 | 83 | function collectNested02NestedA2(selectResult: Nested02Result[]): Nested02NestedA2[] { 84 | const grouped = groupBy(selectResult.filter(r => r.id_3 != null), r => r.id_3); 85 | return [...grouped.values()].map(row => ({ 86 | id: row[0].id_3!, 87 | address: row[0].address_2!, 88 | })) 89 | } 90 | 91 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 92 | return array.reduce((map, value, index, array) => { 93 | const key = predicate(value, index, array); 94 | map.get(key)?.push(value) ?? map.set(key, [value]); 95 | return map; 96 | }, new Map()); 97 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/nested03-many-to-many.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Nested03Result = { 4 | surveyid: number; 5 | surveyname: string; 6 | userid: number; 7 | username: string; 8 | } 9 | 10 | export type Nested03NestedSurveys = { 11 | surveyid: number; 12 | surveyname: string; 13 | users: Nested03NestedUsers[]; 14 | } 15 | 16 | export type Nested03NestedUsers = { 17 | userid: number; 18 | username: string; 19 | } 20 | 21 | export async function nested03(client: pg.Client | pg.Pool): Promise { 22 | const sql = ` 23 | -- @nested 24 | SELECT 25 | s.id as surveyId, 26 | s.name as surveyName, 27 | u.id as userId, 28 | u.name as userName 29 | FROM surveys s 30 | INNER JOIN participants p on p.fk_survey = s.id 31 | INNER JOIN users u on u.id = p.fk_user 32 | ` 33 | return client.query({ text: sql, rowMode: 'array' }) 34 | .then(res => res.rows.map(row => mapArrayToNested03Result(row))); 35 | } 36 | 37 | function mapArrayToNested03Result(data: any) { 38 | const result: Nested03Result = { 39 | surveyid: data[0], 40 | surveyname: data[1], 41 | userid: data[2], 42 | username: data[3] 43 | } 44 | return result; 45 | } 46 | 47 | export async function nested03Nested(client: pg.Client | pg.Pool): Promise { 48 | const selectResult = await nested03(client); 49 | if (selectResult.length == 0) { 50 | return []; 51 | } 52 | return collectNested03NestedSurveys(selectResult); 53 | } 54 | 55 | function collectNested03NestedSurveys(selectResult: Nested03Result[]): Nested03NestedSurveys[] { 56 | const grouped = groupBy(selectResult.filter(r => r.surveyid != null), r => r.surveyid); 57 | return [...grouped.values()].map(row => ({ 58 | surveyid: row[0].surveyid!, 59 | surveyname: row[0].surveyname!, 60 | users: collectNested03NestedUsers(row), 61 | })) 62 | } 63 | 64 | function collectNested03NestedUsers(selectResult: Nested03Result[]): Nested03NestedUsers[] { 65 | const grouped = groupBy(selectResult.filter(r => r.userid != null), r => r.userid); 66 | return [...grouped.values()].map(row => ({ 67 | userid: row[0].userid!, 68 | username: row[0].username!, 69 | })) 70 | } 71 | 72 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 73 | return array.reduce((map, value, index, array) => { 74 | const key = predicate(value, index, array); 75 | map.get(key)?.push(value) ?? map.set(key, [value]); 76 | return map; 77 | }, new Map()); 78 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/select-type-cast.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type SelectTypeCastResult = { 4 | id: number; 5 | } 6 | 7 | export async function selectTypeCast(client: pg.Client | pg.Pool): Promise { 8 | const sql = ` 9 | SELECT id::int2 FROM mytable1 10 | ` 11 | return client.query({ text: sql, rowMode: 'array' }) 12 | .then(res => res.rows.map(row => mapArrayToSelectTypeCastResult(row))); 13 | } 14 | 15 | function mapArrayToSelectTypeCastResult(data: any) { 16 | const result: SelectTypeCastResult = { 17 | id: data[0] 18 | } 19 | return result; 20 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/select01.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Select01Params = { 4 | param1: number; 5 | } 6 | 7 | export type Select01Result = { 8 | id: number; 9 | name?: string; 10 | } 11 | 12 | export async function select01(client: pg.Client | pg.Pool, params: Select01Params): Promise { 13 | const sql = ` 14 | select id, name from mytable2 where id = $1 15 | ` 16 | return client.query({ text: sql, rowMode: 'array', values: [params.param1] }) 17 | .then(res => res.rows.length > 0 ? mapArrayToSelect01Result(res.rows[0]) : null); 18 | } 19 | 20 | function mapArrayToSelect01Result(data: any) { 21 | const result: Select01Result = { 22 | id: data[0], 23 | name: data[1] 24 | } 25 | return result; 26 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/select02.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Select02Result = { 4 | id: number; 5 | } 6 | 7 | export async function select02(client: pg.Client | pg.Pool): Promise { 8 | const sql = ` 9 | select id from mytable1 10 | ` 11 | return client.query({ text: sql, rowMode: 'array' }) 12 | .then(res => res.rows.map(row => mapArrayToSelect02Result(row))); 13 | } 14 | 15 | function mapArrayToSelect02Result(data: any) { 16 | const result: Select02Result = { 17 | id: data[0] 18 | } 19 | return result; 20 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/select03.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Select03Params = { 4 | id: number; 5 | value: number; 6 | } 7 | 8 | export type Select03Result = { 9 | id: number; 10 | } 11 | 12 | export async function select03(client: pg.Client | pg.Pool, params: Select03Params): Promise { 13 | const sql = ` 14 | select id from mytable1 where id = $1 or value = $1 and value = $2 15 | ` 16 | return client.query({ text: sql, rowMode: 'array', values: [params.id, params.value] }) 17 | .then(res => res.rows.map(row => mapArrayToSelect03Result(row))); 18 | } 19 | 20 | function mapArrayToSelect03Result(data: any) { 21 | const result: Select03Result = { 22 | id: data[0] 23 | } 24 | return result; 25 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/select06-2.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Select06Params = { 4 | param1: string; 5 | param2: number[]; 6 | param3: string; 7 | } 8 | 9 | export type Select06Result = { 10 | id: number; 11 | } 12 | 13 | let currentIndex: number; 14 | export async function select06(client: pg.Client | pg.Pool, params: Select06Params): Promise { 15 | currentIndex = 3; 16 | const sql = ` 17 | SELECT id 18 | FROM mytable2 19 | WHERE name = $1 20 | OR id in (${generatePlaceholders('$2', params.param2)}) 21 | OR name = $3 22 | ` 23 | return client.query({ text: sql, rowMode: 'array', values: [params.param1, params.param2[0], params.param3, ...params.param2.slice(1)] }) 24 | .then(res => res.rows.map(row => mapArrayToSelect06Result(row))); 25 | } 26 | 27 | function generatePlaceholders(param: string, paramsArray: any[]): string { 28 | return paramsArray 29 | .map((_, index) => { 30 | if (index === 0) { 31 | return param 32 | } 33 | currentIndex++; 34 | return `$${currentIndex}`; 35 | }) 36 | .join(', '); 37 | } 38 | 39 | function mapArrayToSelect06Result(data: any) { 40 | const result: Select06Result = { 41 | id: data[0] 42 | } 43 | return result; 44 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/select06-any.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Select06Params = { 4 | ids: number[]; 5 | names: string[]; 6 | name: string; 7 | } 8 | 9 | export type Select06Result = { 10 | id: number; 11 | } 12 | 13 | export async function select06(client: pg.Client | pg.Pool, params: Select06Params): Promise { 14 | const sql = ` 15 | SELECT id 16 | FROM mytable2 17 | WHERE id < ANY ($1) 18 | AND name = SOME ($2) 19 | AND name <> $3 20 | ` 21 | return client.query({ text: sql, rowMode: 'array', values: [[...params.ids], [...params.names], params.name] }) 22 | .then(res => res.rows.map(row => mapArrayToSelect06Result(row))); 23 | } 24 | 25 | function mapArrayToSelect06Result(data: any) { 26 | const result: Select06Result = { 27 | id: data[0] 28 | } 29 | return result; 30 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/select06.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Select06Params = { 4 | ids: number[]; 5 | names: string[]; 6 | } 7 | 8 | export type Select06Result = { 9 | id: number; 10 | } 11 | 12 | let currentIndex: number; 13 | export async function select06(client: pg.Client | pg.Pool, params: Select06Params): Promise { 14 | currentIndex = 2; 15 | const sql = ` 16 | SELECT id 17 | FROM mytable2 18 | WHERE id IN (${generatePlaceholders('$1', params.ids)}) 19 | AND name IN (${generatePlaceholders('$2', params.names)}) 20 | ` 21 | return client.query({ text: sql, rowMode: 'array', values: [params.ids[0], params.names[0], ...params.ids.slice(1), ...params.names.slice(1)] }) 22 | .then(res => res.rows.map(row => mapArrayToSelect06Result(row))); 23 | } 24 | 25 | function generatePlaceholders(param: string, paramsArray: any[]): string { 26 | return paramsArray 27 | .map((_, index) => { 28 | if (index === 0) { 29 | return param 30 | } 31 | currentIndex++; 32 | return `$${currentIndex}`; 33 | }) 34 | .join(', '); 35 | } 36 | 37 | function mapArrayToSelect06Result(data: any) { 38 | const result: Select06Result = { 39 | id: data[0] 40 | } 41 | return result; 42 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/select08.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Select08Params = { 4 | param1: boolean; 5 | param2: boolean | null; 6 | } 7 | 8 | export type Select08Result = { 9 | id: number; 10 | param1: boolean; 11 | param2: boolean; 12 | } 13 | 14 | export async function select08(client: pg.Client | pg.Pool, params: Select08Params): Promise { 15 | const sql = ` 16 | SELECT 17 | id, 18 | $1::bool as param1, 19 | $2::bool as param2 20 | FROM mytable1 21 | WHERE $1 is true OR ($2 is true OR $2::bool is null) 22 | ` 23 | return client.query({ text: sql, rowMode: 'array', values: [params.param1, params.param2] }) 24 | .then(res => res.rows.map(row => mapArrayToSelect08Result(row))); 25 | } 26 | 27 | function mapArrayToSelect08Result(data: any) { 28 | const result: Select08Result = { 29 | id: data[0], 30 | param1: data[1] != null ? Boolean(data[1]) : data[1], 31 | param2: data[2] != null ? Boolean(data[2]) : data[2] 32 | } 33 | return result; 34 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/select09-enum-constraint.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Select09Params = { 4 | enum_value: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'; 5 | } 6 | 7 | export type Select09Result = { 8 | enum_constraint: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'; 9 | } 10 | 11 | export async function select09(client: pg.Client | pg.Pool, params: Select09Params): Promise { 12 | const sql = ` 13 | SELECT 14 | enum_constraint 15 | FROM all_types 16 | where enum_constraint = $1 17 | ` 18 | return client.query({ text: sql, rowMode: 'array', values: [params.enum_value] }) 19 | .then(res => res.rows.map(row => mapArrayToSelect09Result(row))); 20 | } 21 | 22 | function mapArrayToSelect09Result(data: any) { 23 | const result: Select09Result = { 24 | enum_constraint: data[0] 25 | } 26 | return result; 27 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/select09-enum.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Select09Params = { 4 | enum_value: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'; 5 | } 6 | 7 | export type Select09Result = { 8 | enum_column: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'; 9 | } 10 | 11 | export async function select09(client: pg.Client | pg.Pool, params: Select09Params): Promise { 12 | const sql = ` 13 | SELECT 14 | enum_column 15 | FROM all_types 16 | where enum_column = $1 17 | ` 18 | return client.query({ text: sql, rowMode: 'array', values: [params.enum_value] }) 19 | .then(res => res.rows.map(row => mapArrayToSelect09Result(row))); 20 | } 21 | 22 | function mapArrayToSelect09Result(data: any) { 23 | const result: Select09Result = { 24 | enum_column: data[0] 25 | } 26 | return result; 27 | } -------------------------------------------------------------------------------- /tests/postgres/expected-code/update01.ts.txt: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | export type Update01Data = { 4 | param1: number | null; 5 | } 6 | 7 | export type Update01Params = { 8 | param2: number; 9 | } 10 | 11 | export type Update01Result = { 12 | rowCount: number; 13 | } 14 | 15 | export async function update01(client: pg.Client | pg.Pool, data: Update01Data, params: Update01Params): Promise { 16 | const sql = ` 17 | UPDATE mytable1 SET value=$1 WHERE id=$2 18 | ` 19 | return client.query({ text: sql, values: [data.param1, params.param2] }) 20 | .then(res => mapArrayToUpdate01Result(res)); 21 | } 22 | 23 | function mapArrayToUpdate01Result(data: any) { 24 | const result: Update01Result = { 25 | rowCount: data.rowCount 26 | } 27 | return result; 28 | } -------------------------------------------------------------------------------- /tests/query-executor.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import { isLeft } from 'fp-ts/lib/Either'; 3 | import { explainSql } from '../src/sqlite-query-analyzer/query-executor'; 4 | import Database from 'better-sqlite3'; 5 | 6 | describe('query-executor tests', () => { 7 | it('explain query with datetime parameter', async () => { 8 | const sql = ` 9 | SELECT * FROM all_types where datetime_column = ? 10 | `; 11 | const db = new Database('./mydb.db'); 12 | const actual = await explainSql(db, sql); 13 | 14 | if (isLeft(actual)) { 15 | assert.fail(`Shouldn't return an error: ${actual.left.description}`); 16 | } 17 | assert.deepStrictEqual(actual.right, true); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/sql-generator.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import type { ColumnSchema } from '../src/mysql-query-analyzer/types'; 3 | import { generateInsertStatement, generateSelectStatement, generateUpdateStatement, generateDeleteStatement } from '../src/sql-generator'; 4 | 5 | describe('code-generator', () => { 6 | const columns: ColumnSchema[] = [ 7 | { 8 | column: 'id', 9 | columnKey: 'PRI', 10 | autoincrement: true, 11 | column_type: 'int', 12 | notNull: true, 13 | table: '', 14 | schema: 'mydb', 15 | hidden: 0 16 | }, 17 | { 18 | column: 'value', 19 | columnKey: '', 20 | autoincrement: false, 21 | column_type: 'int', 22 | notNull: false, 23 | table: '', 24 | schema: 'mydb', 25 | hidden: 0 26 | } 27 | ]; 28 | 29 | it('test scaffolding select stmt', () => { 30 | const actual = generateSelectStatement('mysql2', 'mytable1', columns); 31 | const expected = `SELECT 32 | \`id\`, 33 | \`value\` 34 | FROM mytable1 35 | WHERE \`id\` = :id`; 36 | 37 | assert.deepStrictEqual(actual, expected); 38 | }); 39 | 40 | it('test scaffolding insert stmt', () => { 41 | const actual = generateInsertStatement('mysql2', 'mytable1', columns); 42 | const expected = `INSERT INTO mytable1 43 | ( 44 | \`value\` 45 | ) 46 | VALUES 47 | ( 48 | :value 49 | )`; 50 | assert.deepStrictEqual(actual, expected); 51 | }); 52 | 53 | it('test scaffolding update stmt', () => { 54 | const actual = generateUpdateStatement('mysql2', 'mytable1', columns); 55 | const expected = `UPDATE mytable1 56 | SET 57 | \`value\` = CASE WHEN :valueSet THEN :value ELSE \`value\` END 58 | WHERE 59 | \`id\` = :id`; 60 | 61 | assert.deepStrictEqual(actual, expected); 62 | }); 63 | 64 | it('test scaffolding delete stmt', () => { 65 | const actual = generateDeleteStatement('mysql2', 'mytable1', columns); 66 | const expected = `DELETE FROM mytable1 67 | WHERE \`id\` = :id`; 68 | 69 | assert.deepStrictEqual(actual, expected); 70 | }); 71 | 72 | it('test tablename with whitespace', () => { 73 | const actual = generateSelectStatement('mysql2', 'my table', columns); 74 | const expected = `SELECT 75 | \`id\`, 76 | \`value\` 77 | FROM \`my table\` 78 | WHERE \`id\` = :id`; 79 | 80 | assert.deepStrictEqual(actual, expected); 81 | }); 82 | 83 | it('test scaffolding insert stmt with space in table name', () => { 84 | const actual = generateInsertStatement('mysql2', 'my table', columns); 85 | const expected = `INSERT INTO \`my table\` 86 | ( 87 | \`value\` 88 | ) 89 | VALUES 90 | ( 91 | :value 92 | )`; 93 | assert.deepStrictEqual(actual, expected); 94 | }); 95 | 96 | it('test scaffolding update stmt with space in table name', () => { 97 | const actual = generateUpdateStatement('mysql2', 'my table', columns); 98 | const expected = `UPDATE \`my table\` 99 | SET 100 | \`value\` = CASE WHEN :valueSet THEN :value ELSE \`value\` END 101 | WHERE 102 | \`id\` = :id`; 103 | 104 | assert.deepStrictEqual(actual, expected); 105 | }); 106 | 107 | it('test scaffolding delete stmt with space in table name', () => { 108 | const actual = generateDeleteStatement('mysql2', 'my table', columns); 109 | const expected = `DELETE FROM \`my table\` 110 | WHERE \`id\` = :id`; 111 | 112 | assert.deepStrictEqual(actual, expected); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /tests/sqlite-e2e/sql/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./nested01"; 2 | export * from "./nested02"; 3 | export * from "./nested03"; 4 | export * from "./nested04-without-join-table"; 5 | export * from "./nested04"; 6 | -------------------------------------------------------------------------------- /tests/sqlite-e2e/sql/nested01.sql: -------------------------------------------------------------------------------- 1 | -- @nested 2 | SELECT 3 | u.id as user_id, 4 | u.name as user_name, 5 | p.id as post_id, 6 | p.title as post_title 7 | FROM users u 8 | INNER JOIN posts p on p.fk_user = u.id -------------------------------------------------------------------------------- /tests/sqlite-e2e/sql/nested01.ts: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Nested01Result = { 4 | user_id: number; 5 | user_name: string; 6 | post_id: number; 7 | post_title: string; 8 | } 9 | 10 | export function nested01(db: Database): Nested01Result[] { 11 | const sql = ` 12 | -- @nested 13 | SELECT 14 | u.id as user_id, 15 | u.name as user_name, 16 | p.id as post_id, 17 | p.title as post_title 18 | FROM users u 19 | INNER JOIN posts p on p.fk_user = u.id 20 | ` 21 | return db.prepare(sql) 22 | .raw(true) 23 | .all() 24 | .map(data => mapArrayToNested01Result(data)); 25 | } 26 | 27 | function mapArrayToNested01Result(data: any) { 28 | const result: Nested01Result = { 29 | user_id: data[0], 30 | user_name: data[1], 31 | post_id: data[2], 32 | post_title: data[3] 33 | } 34 | return result; 35 | } 36 | 37 | export type Nested01NestedUsers = { 38 | user_id: number; 39 | user_name: string; 40 | posts: Nested01NestedPosts[]; 41 | } 42 | 43 | export type Nested01NestedPosts = { 44 | post_id: number; 45 | post_title: string; 46 | } 47 | 48 | export function nested01Nested(db: Database): Nested01NestedUsers[] { 49 | const selectResult = nested01(db); 50 | if (selectResult.length == 0) { 51 | return []; 52 | } 53 | return collectNested01NestedUsers(selectResult); 54 | } 55 | 56 | function collectNested01NestedUsers(selectResult: Nested01Result[]): Nested01NestedUsers[] { 57 | const grouped = groupBy(selectResult.filter(r => r.user_id != null), r => r.user_id); 58 | return [...grouped.values()].map(row => ({ 59 | user_id: row[0].user_id!, 60 | user_name: row[0].user_name!, 61 | posts: collectNested01NestedPosts(row), 62 | })) 63 | } 64 | 65 | function collectNested01NestedPosts(selectResult: Nested01Result[]): Nested01NestedPosts[] { 66 | const grouped = groupBy(selectResult.filter(r => r.post_id != null), r => r.post_id); 67 | return [...grouped.values()].map(row => ({ 68 | post_id: row[0].post_id!, 69 | post_title: row[0].post_title!, 70 | })) 71 | } 72 | 73 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 74 | return array.reduce((map, value, index, array) => { 75 | const key = predicate(value, index, array); 76 | map.get(key)?.push(value) ?? map.set(key, [value]); 77 | return map; 78 | }, new Map()); 79 | } -------------------------------------------------------------------------------- /tests/sqlite-e2e/sql/nested02.sql: -------------------------------------------------------------------------------- 1 | -- @nested 2 | SELECT 3 | u.id as user_id, 4 | u.name as user_name, 5 | p.id as post_id, 6 | p.title as post_title, 7 | p.body as post_body, 8 | r.id as role_id, 9 | r.role, 10 | c.id as comment_id, 11 | c.comment 12 | FROM users u 13 | INNER JOIN posts p on p.fk_user = u.id 14 | INNER JOIN roles r on r.fk_user = u.id 15 | INNER JOIN comments c on c.fk_post = p.id -------------------------------------------------------------------------------- /tests/sqlite-e2e/sql/nested03.sql: -------------------------------------------------------------------------------- 1 | -- @nested 2 | SELECT 3 | c.id, 4 | a1.*, 5 | a2.* 6 | FROM clients as c 7 | INNER JOIN addresses as a1 ON a1.id = c.primaryAddress 8 | LEFT JOIN addresses as a2 ON a2.id = c.secondaryAddress 9 | WHERE c.id = ? -------------------------------------------------------------------------------- /tests/sqlite-e2e/sql/nested03.ts: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Nested03Params = { 4 | param1: number; 5 | } 6 | 7 | export type Nested03Result = { 8 | id: number; 9 | id_2: number; 10 | address: string; 11 | id_3?: number; 12 | address_2?: string; 13 | } 14 | 15 | export function nested03(db: Database, params: Nested03Params): Nested03Result[] { 16 | const sql = ` 17 | -- @nested 18 | SELECT 19 | c.id, 20 | a1.*, 21 | a2.* 22 | FROM clients as c 23 | INNER JOIN addresses as a1 ON a1.id = c.primaryAddress 24 | LEFT JOIN addresses as a2 ON a2.id = c.secondaryAddress 25 | WHERE c.id = ? 26 | ` 27 | return db.prepare(sql) 28 | .raw(true) 29 | .all([params.param1]) 30 | .map(data => mapArrayToNested03Result(data)); 31 | } 32 | 33 | function mapArrayToNested03Result(data: any) { 34 | const result: Nested03Result = { 35 | id: data[0], 36 | id_2: data[1], 37 | address: data[2], 38 | id_3: data[3], 39 | address_2: data[4] 40 | } 41 | return result; 42 | } 43 | 44 | export type Nested03NestedC = { 45 | id: number; 46 | a1: Nested03NestedA1; 47 | a2?: Nested03NestedA2; 48 | } 49 | 50 | export type Nested03NestedA1 = { 51 | id: number; 52 | address: string; 53 | } 54 | 55 | export type Nested03NestedA2 = { 56 | id: number; 57 | address: string; 58 | } 59 | 60 | export function nested03Nested(db: Database, params: Nested03Params): Nested03NestedC[] { 61 | const selectResult = nested03(db, params); 62 | if (selectResult.length == 0) { 63 | return []; 64 | } 65 | return collectNested03NestedC(selectResult); 66 | } 67 | 68 | function collectNested03NestedC(selectResult: Nested03Result[]): Nested03NestedC[] { 69 | const grouped = groupBy(selectResult.filter(r => r.id != null), r => r.id); 70 | return [...grouped.values()].map(row => ({ 71 | id: row[0].id!, 72 | a1: collectNested03NestedA1(row)[0], 73 | a2: collectNested03NestedA2(row)[0], 74 | })) 75 | } 76 | 77 | function collectNested03NestedA1(selectResult: Nested03Result[]): Nested03NestedA1[] { 78 | const grouped = groupBy(selectResult.filter(r => r.id_2 != null), r => r.id_2); 79 | return [...grouped.values()].map(row => ({ 80 | id: row[0].id_2!, 81 | address: row[0].address!, 82 | })) 83 | } 84 | 85 | function collectNested03NestedA2(selectResult: Nested03Result[]): Nested03NestedA2[] { 86 | const grouped = groupBy(selectResult.filter(r => r.id_3 != null), r => r.id_3); 87 | return [...grouped.values()].map(row => ({ 88 | id: row[0].id_3!, 89 | address: row[0].address_2!, 90 | })) 91 | } 92 | 93 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 94 | return array.reduce((map, value, index, array) => { 95 | const key = predicate(value, index, array); 96 | map.get(key)?.push(value) ?? map.set(key, [value]); 97 | return map; 98 | }, new Map()); 99 | } -------------------------------------------------------------------------------- /tests/sqlite-e2e/sql/nested04-without-join-table.sql: -------------------------------------------------------------------------------- 1 | -- @nested 2 | SELECT 3 | s.*, 4 | u.* 5 | FROM surveys s 6 | INNER JOIN participants p on p.fk_survey = s.id 7 | INNER JOIN users u on u.id = p.fk_user -------------------------------------------------------------------------------- /tests/sqlite-e2e/sql/nested04-without-join-table.ts: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Nested04WithoutJoinTableResult = { 4 | id: number; 5 | name: string; 6 | id_2: number; 7 | name_2: string; 8 | } 9 | 10 | export function nested04WithoutJoinTable(db: Database): Nested04WithoutJoinTableResult[] { 11 | const sql = ` 12 | -- @nested 13 | SELECT 14 | s.*, 15 | u.* 16 | FROM surveys s 17 | INNER JOIN participants p on p.fk_survey = s.id 18 | INNER JOIN users u on u.id = p.fk_user 19 | ` 20 | return db.prepare(sql) 21 | .raw(true) 22 | .all() 23 | .map(data => mapArrayToNested04WithoutJoinTableResult(data)); 24 | } 25 | 26 | function mapArrayToNested04WithoutJoinTableResult(data: any) { 27 | const result: Nested04WithoutJoinTableResult = { 28 | id: data[0], 29 | name: data[1], 30 | id_2: data[2], 31 | name_2: data[3] 32 | } 33 | return result; 34 | } 35 | 36 | export type Nested04WithoutJoinTableNestedSurveys = { 37 | id: number; 38 | name: string; 39 | users: Nested04WithoutJoinTableNestedUsers[]; 40 | } 41 | 42 | export type Nested04WithoutJoinTableNestedUsers = { 43 | id: number; 44 | name: string; 45 | } 46 | 47 | export function nested04WithoutJoinTableNested(db: Database): Nested04WithoutJoinTableNestedSurveys[] { 48 | const selectResult = nested04WithoutJoinTable(db); 49 | if (selectResult.length == 0) { 50 | return []; 51 | } 52 | return collectNested04WithoutJoinTableNestedSurveys(selectResult); 53 | } 54 | 55 | function collectNested04WithoutJoinTableNestedSurveys(selectResult: Nested04WithoutJoinTableResult[]): Nested04WithoutJoinTableNestedSurveys[] { 56 | const grouped = groupBy(selectResult.filter(r => r.id != null), r => r.id); 57 | return [...grouped.values()].map(row => ({ 58 | id: row[0].id!, 59 | name: row[0].name!, 60 | users: collectNested04WithoutJoinTableNestedUsers(row), 61 | })) 62 | } 63 | 64 | function collectNested04WithoutJoinTableNestedUsers(selectResult: Nested04WithoutJoinTableResult[]): Nested04WithoutJoinTableNestedUsers[] { 65 | const grouped = groupBy(selectResult.filter(r => r.id_2 != null), r => r.id_2); 66 | return [...grouped.values()].map(row => ({ 67 | id: row[0].id_2!, 68 | name: row[0].name_2!, 69 | })) 70 | } 71 | 72 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 73 | return array.reduce((map, value, index, array) => { 74 | const key = predicate(value, index, array); 75 | map.get(key)?.push(value) ?? map.set(key, [value]); 76 | return map; 77 | }, new Map()); 78 | } -------------------------------------------------------------------------------- /tests/sqlite-e2e/sql/nested04.sql: -------------------------------------------------------------------------------- 1 | -- @nested 2 | SELECT 3 | s.id as surveyId, 4 | s.name as surveyName, 5 | p.id as participantId, 6 | u.id as userId, 7 | u.name as userName 8 | FROM surveys s 9 | INNER JOIN participants p on p.fk_survey = s.id 10 | INNER JOIN users u on u.id = p.fk_user -------------------------------------------------------------------------------- /tests/sqlite-e2e/sql/nested04.ts: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Nested04Result = { 4 | surveyId: number; 5 | surveyName: string; 6 | participantId: number; 7 | userId: number; 8 | userName: string; 9 | } 10 | 11 | export function nested04(db: Database): Nested04Result[] { 12 | const sql = ` 13 | -- @nested 14 | SELECT 15 | s.id as surveyId, 16 | s.name as surveyName, 17 | p.id as participantId, 18 | u.id as userId, 19 | u.name as userName 20 | FROM surveys s 21 | INNER JOIN participants p on p.fk_survey = s.id 22 | INNER JOIN users u on u.id = p.fk_user 23 | ` 24 | return db.prepare(sql) 25 | .raw(true) 26 | .all() 27 | .map(data => mapArrayToNested04Result(data)); 28 | } 29 | 30 | function mapArrayToNested04Result(data: any) { 31 | const result: Nested04Result = { 32 | surveyId: data[0], 33 | surveyName: data[1], 34 | participantId: data[2], 35 | userId: data[3], 36 | userName: data[4] 37 | } 38 | return result; 39 | } 40 | 41 | export type Nested04NestedSurveys = { 42 | surveyId: number; 43 | surveyName: string; 44 | participants: Nested04NestedParticipants[]; 45 | } 46 | 47 | export type Nested04NestedParticipants = { 48 | participantId: number; 49 | users: Nested04NestedUsers; 50 | } 51 | 52 | export type Nested04NestedUsers = { 53 | userId: number; 54 | userName: string; 55 | } 56 | 57 | export function nested04Nested(db: Database): Nested04NestedSurveys[] { 58 | const selectResult = nested04(db); 59 | if (selectResult.length == 0) { 60 | return []; 61 | } 62 | return collectNested04NestedSurveys(selectResult); 63 | } 64 | 65 | function collectNested04NestedSurveys(selectResult: Nested04Result[]): Nested04NestedSurveys[] { 66 | const grouped = groupBy(selectResult.filter(r => r.surveyId != null), r => r.surveyId); 67 | return [...grouped.values()].map(row => ({ 68 | surveyId: row[0].surveyId!, 69 | surveyName: row[0].surveyName!, 70 | participants: collectNested04NestedParticipants(row), 71 | })) 72 | } 73 | 74 | function collectNested04NestedParticipants(selectResult: Nested04Result[]): Nested04NestedParticipants[] { 75 | const grouped = groupBy(selectResult.filter(r => r.participantId != null), r => r.participantId); 76 | return [...grouped.values()].map(row => ({ 77 | participantId: row[0].participantId!, 78 | users: collectNested04NestedUsers(row)[0], 79 | })) 80 | } 81 | 82 | function collectNested04NestedUsers(selectResult: Nested04Result[]): Nested04NestedUsers[] { 83 | const grouped = groupBy(selectResult.filter(r => r.userId != null), r => r.userId); 84 | return [...grouped.values()].map(row => ({ 85 | userId: row[0].userId!, 86 | userName: row[0].userName!, 87 | })) 88 | } 89 | 90 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 91 | return array.reduce((map, value, index, array) => { 92 | const key = predicate(value, index, array); 93 | map.get(key)?.push(value) ?? map.set(key, [value]); 94 | return map; 95 | }, new Map()); 96 | } -------------------------------------------------------------------------------- /tests/sqlite/enum-parser.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import { enumParser } from '../../src/sqlite-query-analyzer/enum-parser'; 3 | import { EnumMap } from '../../src/sqlite-query-analyzer/types'; 4 | 5 | describe('enum-parser', () => { 6 | 7 | const createTableStmt = `CREATE TABLE all_types ( 8 | int_column INT, 9 | integer_column INTEGER, 10 | tinyiny_column TINYINT, 11 | smallint_column SMALLINT, 12 | mediumint_column MEDIUMINT, 13 | bigint_column BIGINT, 14 | unsignedbigint_column UNSIGNED BIGINT, 15 | int2_column INT2, 16 | int8_column INT8, 17 | character_column CHARACTER(20), 18 | varchar_column VARCHAR(255), 19 | varyingcharacter_column VARYING CHARACTER(255), 20 | nchar_column NCHAR(55), 21 | native_character_column NATIVE CHARACTER(70), 22 | nvarchar_column NVARCHAR(100), 23 | text_column TEXT, 24 | clob_column CLOB, 25 | blob_column BLOB, 26 | blob_column2, 27 | real_column REAL, 28 | double_column DOUBLE, 29 | doubleprecision_column DOUBLE PRECISION, 30 | float_column FLOAT, 31 | numeric_column NUMERIC, 32 | decimal_column DECIMAL(10,5), 33 | boolean_column BOOLEAN, 34 | date_column DATE, 35 | datetime_column DATETIME, 36 | integer_column_default INTEGER DEFAULT 10 37 | , enum_column TEXT CHECK(enum_column IN ('x-small', 'small', 'medium', 'large', 'x-large')));CREATE TABLE enum_types( 38 | id INTEGER PRIMARY KEY, 39 | column1 TEXT CHECK(column1 IN ('A', 'B', 'C')), 40 | column2 INTEGER CHECK(column2 IN (1, 2)), 41 | column3 TEXT CHECK(column3 NOT IN ('A', 'B', 'C')), 42 | column4 TEXT CHECK(column4 LIKE '%a%'), 43 | column5 NOT NULL CHECK(column5 IN ('D', 'E')) 44 | );CREATE TABLE enum_types2( 45 | id INTEGER PRIMARY KEY, 46 | column1 TEXT CHECK ( column1 IN ('f', 'g') ) 47 | )` 48 | 49 | it('enum-parser', () => { 50 | const actual = enumParser(createTableStmt); 51 | const expected: EnumMap = { 52 | all_types: { 53 | enum_column: `ENUM('x-small','small','medium','large','x-large')` 54 | }, 55 | enum_types: { 56 | column1: `ENUM('A','B','C')`, 57 | column5: `ENUM('D','E')` 58 | }, 59 | enum_types2: { 60 | column1: `ENUM('f','g')` 61 | } 62 | } 63 | assert.deepStrictEqual(actual, expected); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-delete01-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type DeleteFromMytable1Params = { 4 | id: number; 5 | } 6 | 7 | export type DeleteFromMytable1Result = { 8 | changes: number; 9 | } 10 | 11 | export function deleteFromMytable1(db: Database, params: DeleteFromMytable1Params): DeleteFromMytable1Result { 12 | 13 | const sql = `DELETE 14 | FROM mytable1 15 | WHERE id = ?` 16 | 17 | return db.prepare(sql) 18 | .run(params.id) as DeleteFromMytable1Result; 19 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-delete01-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type DeleteFromMytable1Params = { 4 | id: number; 5 | } 6 | 7 | export type DeleteFromMytable1Result = { 8 | changes: number; 9 | } 10 | 11 | export async function deleteFromMytable1(db: D1Database, params: DeleteFromMytable1Params): Promise { 12 | 13 | const sql = `DELETE 14 | FROM mytable1 15 | WHERE id = ?` 16 | 17 | return db.prepare(sql) 18 | .bind(params.id) 19 | .run() 20 | .then(res => res.meta); 21 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-delete01-libsql.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Client, Transaction } from '@libsql/client'; 2 | 3 | export type DeleteFromMytable1Params = { 4 | id: number; 5 | } 6 | 7 | export type DeleteFromMytable1Result = { 8 | rowsAffected: number; 9 | } 10 | 11 | export async function deleteFromMytable1(client: Client | Transaction, params: DeleteFromMytable1Params): Promise { 12 | 13 | const sql = `DELETE 14 | FROM mytable1 15 | WHERE id = ?` 16 | 17 | return client.execute({ sql, args: [params.id] }) 18 | .then(res => mapArrayToDeleteFromMytable1Result(res)); 19 | } 20 | 21 | function mapArrayToDeleteFromMytable1Result(data: any) { 22 | const result: DeleteFromMytable1Result = { 23 | rowsAffected: data.rowsAffected 24 | } 25 | return result; 26 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-delete01.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type DeleteFromMytable1Params = { 4 | id: number; 5 | } 6 | 7 | export type DeleteFromMytable1Result = { 8 | changes: number; 9 | } 10 | 11 | export function deleteFromMytable1(db: Database, params: DeleteFromMytable1Params): DeleteFromMytable1Result { 12 | 13 | const sql = `DELETE 14 | FROM mytable1 15 | WHERE id = ?` 16 | 17 | return db.prepare(sql) 18 | .run([params.id]) as DeleteFromMytable1Result; 19 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-insert01-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type InsertIntoMytable1Params = { 4 | id?: number; 5 | value?: number | null; 6 | } 7 | 8 | export type InsertIntoMytable1Result = { 9 | changes: number; 10 | lastInsertRowid: number; 11 | } 12 | 13 | export function insertIntoMytable1(db: Database, params: InsertIntoMytable1Params): InsertIntoMytable1Result { 14 | 15 | const keys = Object.keys(params) as Array; 16 | const columns = keys.filter(key => params[key] !== undefined); 17 | const values = columns.map(col => params[col]!); 18 | 19 | const sql = columns.length == 0 20 | ? `INSERT INTO mytable1 DEFAULT VALUES` 21 | : `INSERT INTO mytable1(${columns.join(',')}) VALUES(${columns.map(_ => '?').join(',')})` 22 | 23 | return db.prepare(sql) 24 | .run(...values) as InsertIntoMytable1Result; 25 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-insert01-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type InsertIntoMytable1Params = { 4 | id?: number; 5 | value?: number | null; 6 | } 7 | 8 | export type InsertIntoMytable1Result = { 9 | changes: number; 10 | last_row_id: number; 11 | } 12 | 13 | export async function insertIntoMytable1(db: D1Database, params: InsertIntoMytable1Params): Promise { 14 | 15 | const keys = Object.keys(params) as Array; 16 | const columns = keys.filter(key => params[key] !== undefined); 17 | const values = columns.map(col => params[col]!); 18 | 19 | const sql = columns.length == 0 20 | ? `INSERT INTO mytable1 DEFAULT VALUES` 21 | : `INSERT INTO mytable1(${columns.join(',')}) VALUES(${columns.map(_ => '?').join(',')})` 22 | 23 | return db.prepare(sql) 24 | .bind(...values) 25 | .run() 26 | .then(res => res.meta); 27 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-insert01-libsql.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Client, Transaction } from '@libsql/client'; 2 | 3 | export type InsertIntoMytable1Params = { 4 | id?: number; 5 | value?: number | null; 6 | } 7 | 8 | export type InsertIntoMytable1Result = { 9 | rowsAffected: number; 10 | lastInsertRowid: number; 11 | } 12 | 13 | export async function insertIntoMytable1(client: Client | Transaction, params: InsertIntoMytable1Params): Promise { 14 | 15 | const keys = Object.keys(params) as Array; 16 | const columns = keys.filter(key => params[key] !== undefined); 17 | const values = columns.map(col => params[col]!); 18 | 19 | const sql = columns.length == 0 20 | ? `INSERT INTO mytable1 DEFAULT VALUES` 21 | : `INSERT INTO mytable1(${columns.join(',')}) VALUES(${columns.map(_ => '?').join(',')})` 22 | 23 | return client.execute({ sql, args: values }) 24 | .then(res => mapArrayToInsertIntoMytable1Result(res)); 25 | } 26 | 27 | function mapArrayToInsertIntoMytable1Result(data: any) { 28 | const result: InsertIntoMytable1Result = { 29 | rowsAffected: data.rowsAffected, 30 | lastInsertRowid: data.lastInsertRowid 31 | } 32 | return result; 33 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-insert01.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type InsertIntoMytable1Params = { 4 | id?: number; 5 | value?: number | null; 6 | } 7 | 8 | export type InsertIntoMytable1Result = { 9 | changes: number; 10 | lastInsertRowid: number; 11 | } 12 | 13 | export function insertIntoMytable1(db: Database, params: InsertIntoMytable1Params): InsertIntoMytable1Result { 14 | 15 | const keys = Object.keys(params) as Array; 16 | const columns = keys.filter(key => params[key] !== undefined); 17 | const values = columns.map(col => params[col]!); 18 | 19 | const sql = columns.length == 0 20 | ? `INSERT INTO mytable1 DEFAULT VALUES` 21 | : `INSERT INTO mytable1(${columns.join(',')}) VALUES(${columns.map(_ => '?').join(',')})` 22 | 23 | return db.prepare(sql) 24 | .run(values) as InsertIntoMytable1Result; 25 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-select01-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type SelectFromMytable1Params = { 4 | id: number; 5 | } 6 | 7 | export type SelectFromMytable1Result = { 8 | id: number; 9 | value?: number; 10 | } 11 | 12 | export function selectFromMytable1(db: Database, params: SelectFromMytable1Params): SelectFromMytable1Result | null { 13 | 14 | const sql = `SELECT 15 | id, 16 | value 17 | FROM mytable1 18 | WHERE id = ?` 19 | 20 | return db.prepare(sql) 21 | .values(params.id) 22 | .map(data => mapArrayToSelectFromMytable1Result(data))[0]; 23 | } 24 | 25 | function mapArrayToSelectFromMytable1Result(data: any) { 26 | const result: SelectFromMytable1Result = { 27 | id: data[0], 28 | value: data[1] 29 | } 30 | return result; 31 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-select01-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type SelectFromMytable1Params = { 4 | id: number; 5 | } 6 | 7 | export type SelectFromMytable1Result = { 8 | id: number; 9 | value?: number; 10 | } 11 | 12 | export async function selectFromMytable1(db: D1Database, params: SelectFromMytable1Params): Promise { 13 | 14 | const sql = `SELECT 15 | id, 16 | value 17 | FROM mytable1 18 | WHERE id = ?` 19 | 20 | return db.prepare(sql) 21 | .bind(params.id) 22 | .first(); 23 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-select01-libsql.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Client, Transaction } from '@libsql/client'; 2 | 3 | export type SelectFromMytable1Params = { 4 | id: number; 5 | } 6 | 7 | export type SelectFromMytable1Result = { 8 | id: number; 9 | value?: number; 10 | } 11 | 12 | export async function selectFromMytable1(client: Client | Transaction, params: SelectFromMytable1Params): Promise { 13 | 14 | const sql = `SELECT 15 | id, 16 | value 17 | FROM mytable1 18 | WHERE id = ?` 19 | 20 | return client.execute({ sql, args: [params.id] }) 21 | .then(res => res.rows) 22 | .then(rows => mapArrayToSelectFromMytable1Result(rows[0])); 23 | } 24 | 25 | function mapArrayToSelectFromMytable1Result(data: any) { 26 | const result: SelectFromMytable1Result = { 27 | id: data[0], 28 | value: data[1] 29 | } 30 | return result; 31 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-select01.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type SelectFromMytable1Params = { 4 | id: number; 5 | } 6 | 7 | export type SelectFromMytable1Result = { 8 | id: number; 9 | value?: number; 10 | } 11 | 12 | export function selectFromMytable1(db: Database, params: SelectFromMytable1Params): SelectFromMytable1Result | null { 13 | 14 | const sql = `SELECT 15 | id, 16 | value 17 | FROM mytable1 18 | WHERE id = ?` 19 | 20 | return db.prepare(sql) 21 | .raw(true) 22 | .all([params.id]) 23 | .map(data => mapArrayToSelectFromMytable1Result(data))[0]; 24 | } 25 | 26 | function mapArrayToSelectFromMytable1Result(data: any) { 27 | const result: SelectFromMytable1Result = { 28 | id: data[0], 29 | value: data[1] 30 | } 31 | return result; 32 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-update01-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type UpdateMytable1Data = { 4 | value?: number | null; 5 | } 6 | 7 | export type UpdateMytable1Params = { 8 | id: number; 9 | } 10 | 11 | export type UpdateMytable1Result = { 12 | changes: number; 13 | } 14 | 15 | export function updateMytable1(db: Database, data: UpdateMytable1Data, params: UpdateMytable1Params): UpdateMytable1Result { 16 | 17 | const keys = Object.keys(data) as Array; 18 | const columns = keys.filter(key => data[key] !== undefined); 19 | const values = columns.map(col => data[col]!).concat(params.id); 20 | 21 | const sql = ` 22 | UPDATE mytable1 23 | SET ${columns.map(col => `${col} = ?`).join(', ')} 24 | WHERE id = ?` 25 | 26 | return db.prepare(sql) 27 | .run(...values) as UpdateMytable1Result; 28 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-update01-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type UpdateMytable1Data = { 4 | value?: number | null; 5 | } 6 | 7 | export type UpdateMytable1Params = { 8 | id: number; 9 | } 10 | 11 | export type UpdateMytable1Result = { 12 | changes: number; 13 | } 14 | 15 | export async function updateMytable1(db: D1Database, data: UpdateMytable1Data, params: UpdateMytable1Params): Promise { 16 | 17 | const keys = Object.keys(data) as Array; 18 | const columns = keys.filter(key => data[key] !== undefined); 19 | const values = columns.map(col => data[col]!).concat(params.id); 20 | 21 | const sql = ` 22 | UPDATE mytable1 23 | SET ${columns.map(col => `${col} = ?`).join(', ')} 24 | WHERE id = ?` 25 | 26 | return db.prepare(sql) 27 | .bind(params.id) 28 | .run() 29 | .then(res => res.meta); 30 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-update01-libsql.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Client, Transaction } from '@libsql/client'; 2 | 3 | export type UpdateMytable1Data = { 4 | value?: number | null; 5 | } 6 | 7 | export type UpdateMytable1Params = { 8 | id: number; 9 | } 10 | 11 | export type UpdateMytable1Result = { 12 | rowsAffected: number; 13 | } 14 | 15 | export async function updateMytable1(client: Client | Transaction, data: UpdateMytable1Data, params: UpdateMytable1Params): Promise { 16 | 17 | const keys = Object.keys(data) as Array; 18 | const columns = keys.filter(key => data[key] !== undefined); 19 | const values = columns.map(col => data[col]!).concat(params.id); 20 | 21 | const sql = ` 22 | UPDATE mytable1 23 | SET ${columns.map(col => `${col} = ?`).join(', ')} 24 | WHERE id = ?` 25 | 26 | return client.execute({ sql, args: values }) 27 | .then(res => mapArrayToUpdateMytable1Result(res)); 28 | } 29 | 30 | function mapArrayToUpdateMytable1Result(data: any) { 31 | const result: UpdateMytable1Result = { 32 | rowsAffected: data.rowsAffected 33 | } 34 | return result; 35 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/crud-update01.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type UpdateMytable1Data = { 4 | value?: number | null; 5 | } 6 | 7 | export type UpdateMytable1Params = { 8 | id: number; 9 | } 10 | 11 | export type UpdateMytable1Result = { 12 | changes: number; 13 | } 14 | 15 | export function updateMytable1(db: Database, data: UpdateMytable1Data, params: UpdateMytable1Params): UpdateMytable1Result { 16 | 17 | const keys = Object.keys(data) as Array; 18 | const columns = keys.filter(key => data[key] !== undefined); 19 | const values = columns.map(col => data[col]!).concat(params.id); 20 | 21 | const sql = ` 22 | UPDATE mytable1 23 | SET ${columns.map(col => `${col} = ?`).join(', ')} 24 | WHERE id = ?` 25 | 26 | return db.prepare(sql) 27 | .run(values) as UpdateMytable1Result; 28 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/delete01-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Delete01Params = { 4 | param1: number; 5 | } 6 | 7 | export type Delete01Result = { 8 | changes: number; 9 | } 10 | 11 | export function delete01(db: Database, params: Delete01Params): Delete01Result { 12 | const sql = ` 13 | DELETE FROM mytable1 WHERE id=? 14 | ` 15 | return db.prepare(sql) 16 | .run(params.param1) as Delete01Result; 17 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/delete01-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type Delete01Params = { 4 | param1: number; 5 | } 6 | 7 | export type Delete01Result = { 8 | changes: number; 9 | } 10 | 11 | export async function delete01(db: D1Database, params: Delete01Params): Promise { 12 | const sql = ` 13 | DELETE FROM mytable1 WHERE id=? 14 | ` 15 | return db.prepare(sql) 16 | .bind(params.param1) 17 | .run() 18 | .then(res => res.meta); 19 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/delete01-libsql.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Client, Transaction } from '@libsql/client'; 2 | 3 | export type Delete01Params = { 4 | param1: number; 5 | } 6 | 7 | export type Delete01Result = { 8 | rowsAffected: number; 9 | } 10 | 11 | export async function delete01(client: Client | Transaction, params: Delete01Params): Promise { 12 | const sql = ` 13 | DELETE FROM mytable1 WHERE id=? 14 | ` 15 | return client.execute({ sql, args: [params.param1] }) 16 | .then(res => mapArrayToDelete01Result(res)); 17 | } 18 | 19 | function mapArrayToDelete01Result(data: any) { 20 | const result: Delete01Result = { 21 | rowsAffected: data.rowsAffected 22 | } 23 | return result; 24 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/delete01.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Delete01Params = { 4 | param1: number; 5 | } 6 | 7 | export type Delete01Result = { 8 | changes: number; 9 | } 10 | 11 | export function delete01(db: Database, params: Delete01Params): Delete01Result { 12 | const sql = ` 13 | DELETE FROM mytable1 WHERE id=? 14 | ` 15 | return db.prepare(sql) 16 | .run([params.param1]) as Delete01Result; 17 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/insert01-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Insert01Result = { 4 | changes: number; 5 | lastInsertRowid: number; 6 | } 7 | 8 | export function insert01(db: Database): Insert01Result { 9 | const sql = ` 10 | INSERT INTO mytable1(value) values(10) 11 | ` 12 | return db.prepare(sql) 13 | .run() as Insert01Result; 14 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/insert01-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type Insert01Result = { 4 | changes: number; 5 | last_row_id: number; 6 | } 7 | 8 | export async function insert01(db: D1Database): Promise { 9 | const sql = ` 10 | INSERT INTO mytable1(value) values(10) 11 | ` 12 | return db.prepare(sql) 13 | .run() 14 | .then(res => res.meta); 15 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/insert01-libsql.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Client, Transaction } from '@libsql/client'; 2 | 3 | export type Insert01Result = { 4 | rowsAffected: number; 5 | lastInsertRowid: number; 6 | } 7 | 8 | export async function insert01(client: Client | Transaction): Promise { 9 | const sql = ` 10 | INSERT INTO mytable1(value) values(10) 11 | ` 12 | return client.execute(sql) 13 | .then(res => mapArrayToInsert01Result(res)); 14 | } 15 | 16 | function mapArrayToInsert01Result(data: any) { 17 | const result: Insert01Result = { 18 | rowsAffected: data.rowsAffected, 19 | lastInsertRowid: data.lastInsertRowid 20 | } 21 | return result; 22 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/insert01.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Insert01Result = { 4 | changes: number; 5 | lastInsertRowid: number; 6 | } 7 | 8 | export function insert01(db: Database): Insert01Result { 9 | const sql = ` 10 | INSERT INTO mytable1(value) values(10) 11 | ` 12 | return db.prepare(sql) 13 | .run() as Insert01Result; 14 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/insert02-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Insert02Params = { 4 | param1: number | null; 5 | } 6 | 7 | export type Insert02Result = { 8 | changes: number; 9 | lastInsertRowid: number; 10 | } 11 | 12 | export function insert02(db: Database, params: Insert02Params): Insert02Result { 13 | const sql = ` 14 | INSERT INTO mytable1(value) values(?) 15 | ` 16 | return db.prepare(sql) 17 | .run(params.param1) as Insert02Result; 18 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/insert02-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type Insert02Params = { 4 | param1: number | null; 5 | } 6 | 7 | export type Insert02Result = { 8 | changes: number; 9 | last_row_id: number; 10 | } 11 | 12 | export async function insert02(db: D1Database, params: Insert02Params): Promise { 13 | const sql = ` 14 | INSERT INTO mytable1(value) values(?) 15 | ` 16 | return db.prepare(sql) 17 | .bind(params.param1) 18 | .run() 19 | .then(res => res.meta); 20 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/insert02.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Insert02Params = { 4 | param1: number | null; 5 | } 6 | 7 | export type Insert02Result = { 8 | changes: number; 9 | lastInsertRowid: number; 10 | } 11 | 12 | export function insert02(db: Database, params: Insert02Params): Insert02Result { 13 | const sql = ` 14 | INSERT INTO mytable1(value) values(?) 15 | ` 16 | return db.prepare(sql) 17 | .run([params.param1]) as Insert02Result; 18 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/insert03-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type Insert03Params = { 4 | value: number | null; 5 | } 6 | 7 | export type Insert03Result = { 8 | id: number; 9 | value?: number; 10 | } 11 | 12 | export async function insert03(db: D1Database, params: Insert03Params): Promise { 13 | const sql = ` 14 | INSERT INTO mytable1(value) VALUES(?) RETURNING * 15 | ` 16 | return db.prepare(sql) 17 | .bind(params.value) 18 | .raw({ columnNames: false }) 19 | .then(rows => rows.map(row => mapArrayToInsert03Result(row))[0]); 20 | } 21 | 22 | function mapArrayToInsert03Result(data: any) { 23 | const result: Insert03Result = { 24 | id: data[0], 25 | value: data[1] 26 | } 27 | return result; 28 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/insert03-libsql.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Client, Transaction } from '@libsql/client'; 2 | 3 | export type Insert03Params = { 4 | value: number | null; 5 | } 6 | 7 | export type Insert03Result = { 8 | id: number; 9 | value?: number; 10 | } 11 | 12 | export async function insert03(client: Client | Transaction, params: Insert03Params): Promise { 13 | const sql = ` 14 | INSERT INTO mytable1(value) VALUES(?) RETURNING * 15 | ` 16 | return client.execute({ sql, args: [params.value] }) 17 | .then(res => res.rows) 18 | .then(rows => mapArrayToInsert03Result(rows[0])); 19 | } 20 | 21 | function mapArrayToInsert03Result(data: any) { 22 | const result: Insert03Result = { 23 | id: data[0], 24 | value: data[1] 25 | } 26 | return result; 27 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/nested01-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Nested01Result = { 4 | user_id: number; 5 | user_name: string; 6 | post_id: number; 7 | post_title: string; 8 | } 9 | 10 | export function nested01(db: Database): Nested01Result[] { 11 | const sql = ` 12 | -- @nested 13 | SELECT 14 | u.id as user_id, 15 | u.name as user_name, 16 | p.id as post_id, 17 | p.title as post_title 18 | FROM users u 19 | INNER JOIN posts p on p.fk_user = u.id 20 | ` 21 | return db.prepare(sql) 22 | .values() 23 | .map(data => mapArrayToNested01Result(data)); 24 | } 25 | 26 | function mapArrayToNested01Result(data: any) { 27 | const result: Nested01Result = { 28 | user_id: data[0], 29 | user_name: data[1], 30 | post_id: data[2], 31 | post_title: data[3] 32 | } 33 | return result; 34 | } 35 | 36 | export type Nested01NestedUsers = { 37 | user_id: number; 38 | user_name: string; 39 | posts: Nested01NestedPosts[]; 40 | } 41 | 42 | export type Nested01NestedPosts = { 43 | post_id: number; 44 | post_title: string; 45 | } 46 | 47 | export function nested01Nested(db: Database): Nested01NestedUsers[] { 48 | const selectResult = nested01(db); 49 | if (selectResult.length == 0) { 50 | return []; 51 | } 52 | return collectNested01NestedUsers(selectResult); 53 | } 54 | 55 | function collectNested01NestedUsers(selectResult: Nested01Result[]): Nested01NestedUsers[] { 56 | const grouped = groupBy(selectResult.filter(r => r.user_id != null), r => r.user_id); 57 | return [...grouped.values()].map(row => ({ 58 | user_id: row[0].user_id!, 59 | user_name: row[0].user_name!, 60 | posts: collectNested01NestedPosts(row), 61 | })) 62 | } 63 | 64 | function collectNested01NestedPosts(selectResult: Nested01Result[]): Nested01NestedPosts[] { 65 | const grouped = groupBy(selectResult.filter(r => r.post_id != null), r => r.post_id); 66 | return [...grouped.values()].map(row => ({ 67 | post_id: row[0].post_id!, 68 | post_title: row[0].post_title!, 69 | })) 70 | } 71 | 72 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 73 | return array.reduce((map, value, index, array) => { 74 | const key = predicate(value, index, array); 75 | map.get(key)?.push(value) ?? map.set(key, [value]); 76 | return map; 77 | }, new Map()); 78 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/nested01-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type Nested01Result = { 4 | user_id: number; 5 | user_name: string; 6 | post_id: number; 7 | post_title: string; 8 | } 9 | 10 | export async function nested01(db: D1Database): Promise { 11 | const sql = ` 12 | -- @nested 13 | SELECT 14 | u.id as user_id, 15 | u.name as user_name, 16 | p.id as post_id, 17 | p.title as post_title 18 | FROM users u 19 | INNER JOIN posts p on p.fk_user = u.id 20 | ` 21 | return db.prepare(sql) 22 | .raw({ columnNames: false }) 23 | .then(rows => rows.map(row => mapArrayToNested01Result(row))); 24 | } 25 | 26 | function mapArrayToNested01Result(data: any) { 27 | const result: Nested01Result = { 28 | user_id: data[0], 29 | user_name: data[1], 30 | post_id: data[2], 31 | post_title: data[3] 32 | } 33 | return result; 34 | } 35 | 36 | export type Nested01NestedUsers = { 37 | user_id: number; 38 | user_name: string; 39 | posts: Nested01NestedPosts[]; 40 | } 41 | 42 | export type Nested01NestedPosts = { 43 | post_id: number; 44 | post_title: string; 45 | } 46 | 47 | export async function nested01Nested(db: D1Database): Promise { 48 | const selectResult = await nested01(db); 49 | if (selectResult.length == 0) { 50 | return []; 51 | } 52 | return collectNested01NestedUsers(selectResult); 53 | } 54 | 55 | function collectNested01NestedUsers(selectResult: Nested01Result[]): Nested01NestedUsers[] { 56 | const grouped = groupBy(selectResult.filter(r => r.user_id != null), r => r.user_id); 57 | return [...grouped.values()].map(row => ({ 58 | user_id: row[0].user_id!, 59 | user_name: row[0].user_name!, 60 | posts: collectNested01NestedPosts(row), 61 | })) 62 | } 63 | 64 | function collectNested01NestedPosts(selectResult: Nested01Result[]): Nested01NestedPosts[] { 65 | const grouped = groupBy(selectResult.filter(r => r.post_id != null), r => r.post_id); 66 | return [...grouped.values()].map(row => ({ 67 | post_id: row[0].post_id!, 68 | post_title: row[0].post_title!, 69 | })) 70 | } 71 | 72 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 73 | return array.reduce((map, value, index, array) => { 74 | const key = predicate(value, index, array); 75 | map.get(key)?.push(value) ?? map.set(key, [value]); 76 | return map; 77 | }, new Map()); 78 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/nested01-libsql.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Client, Transaction } from '@libsql/client'; 2 | 3 | export type Nested01Result = { 4 | user_id: number; 5 | user_name: string; 6 | post_id: number; 7 | post_title: string; 8 | } 9 | 10 | export async function nested01(client: Client | Transaction): Promise { 11 | const sql = ` 12 | -- @nested 13 | SELECT 14 | u.id as user_id, 15 | u.name as user_name, 16 | p.id as post_id, 17 | p.title as post_title 18 | FROM users u 19 | INNER JOIN posts p on p.fk_user = u.id 20 | ` 21 | return client.execute(sql) 22 | .then(res => res.rows) 23 | .then(rows => rows.map(row => mapArrayToNested01Result(row))); 24 | } 25 | 26 | function mapArrayToNested01Result(data: any) { 27 | const result: Nested01Result = { 28 | user_id: data[0], 29 | user_name: data[1], 30 | post_id: data[2], 31 | post_title: data[3] 32 | } 33 | return result; 34 | } 35 | 36 | export type Nested01NestedUsers = { 37 | user_id: number; 38 | user_name: string; 39 | posts: Nested01NestedPosts[]; 40 | } 41 | 42 | export type Nested01NestedPosts = { 43 | post_id: number; 44 | post_title: string; 45 | } 46 | 47 | export async function nested01Nested(client: Client | Transaction): Promise { 48 | const selectResult = await nested01(client); 49 | if (selectResult.length == 0) { 50 | return []; 51 | } 52 | return collectNested01NestedUsers(selectResult); 53 | } 54 | 55 | function collectNested01NestedUsers(selectResult: Nested01Result[]): Nested01NestedUsers[] { 56 | const grouped = groupBy(selectResult.filter(r => r.user_id != null), r => r.user_id); 57 | return [...grouped.values()].map(row => ({ 58 | user_id: row[0].user_id!, 59 | user_name: row[0].user_name!, 60 | posts: collectNested01NestedPosts(row), 61 | })) 62 | } 63 | 64 | function collectNested01NestedPosts(selectResult: Nested01Result[]): Nested01NestedPosts[] { 65 | const grouped = groupBy(selectResult.filter(r => r.post_id != null), r => r.post_id); 66 | return [...grouped.values()].map(row => ({ 67 | post_id: row[0].post_id!, 68 | post_title: row[0].post_title!, 69 | })) 70 | } 71 | 72 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 73 | return array.reduce((map, value, index, array) => { 74 | const key = predicate(value, index, array); 75 | map.get(key)?.push(value) ?? map.set(key, [value]); 76 | return map; 77 | }, new Map()); 78 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/nested01.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Nested01Result = { 4 | user_id: number; 5 | user_name: string; 6 | post_id: number; 7 | post_title: string; 8 | } 9 | 10 | export function nested01(db: Database): Nested01Result[] { 11 | const sql = ` 12 | -- @nested 13 | SELECT 14 | u.id as user_id, 15 | u.name as user_name, 16 | p.id as post_id, 17 | p.title as post_title 18 | FROM users u 19 | INNER JOIN posts p on p.fk_user = u.id 20 | ` 21 | return db.prepare(sql) 22 | .raw(true) 23 | .all() 24 | .map(data => mapArrayToNested01Result(data)); 25 | } 26 | 27 | function mapArrayToNested01Result(data: any) { 28 | const result: Nested01Result = { 29 | user_id: data[0], 30 | user_name: data[1], 31 | post_id: data[2], 32 | post_title: data[3] 33 | } 34 | return result; 35 | } 36 | 37 | export type Nested01NestedUsers = { 38 | user_id: number; 39 | user_name: string; 40 | posts: Nested01NestedPosts[]; 41 | } 42 | 43 | export type Nested01NestedPosts = { 44 | post_id: number; 45 | post_title: string; 46 | } 47 | 48 | export function nested01Nested(db: Database): Nested01NestedUsers[] { 49 | const selectResult = nested01(db); 50 | if (selectResult.length == 0) { 51 | return []; 52 | } 53 | return collectNested01NestedUsers(selectResult); 54 | } 55 | 56 | function collectNested01NestedUsers(selectResult: Nested01Result[]): Nested01NestedUsers[] { 57 | const grouped = groupBy(selectResult.filter(r => r.user_id != null), r => r.user_id); 58 | return [...grouped.values()].map(row => ({ 59 | user_id: row[0].user_id!, 60 | user_name: row[0].user_name!, 61 | posts: collectNested01NestedPosts(row), 62 | })) 63 | } 64 | 65 | function collectNested01NestedPosts(selectResult: Nested01Result[]): Nested01NestedPosts[] { 66 | const grouped = groupBy(selectResult.filter(r => r.post_id != null), r => r.post_id); 67 | return [...grouped.values()].map(row => ({ 68 | post_id: row[0].post_id!, 69 | post_title: row[0].post_title!, 70 | })) 71 | } 72 | 73 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 74 | return array.reduce((map, value, index, array) => { 75 | const key = predicate(value, index, array); 76 | map.get(key)?.push(value) ?? map.set(key, [value]); 77 | return map; 78 | }, new Map()); 79 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/nested02-bun-clients-with-addresses.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Nested02Params = { 4 | clientId: number; 5 | } 6 | 7 | export type Nested02Result = { 8 | id: number; 9 | id_2: number; 10 | address: string; 11 | id_3?: number; 12 | address_2?: string; 13 | } 14 | 15 | export function nested02(db: Database, params: Nested02Params): Nested02Result[] { 16 | const sql = ` 17 | -- @nested 18 | SELECT 19 | c.id, 20 | a1.*, 21 | a2.* 22 | FROM clients as c 23 | INNER JOIN addresses as a1 ON a1.id = c.primaryAddress 24 | LEFT JOIN addresses as a2 ON a2.id = c.secondaryAddress 25 | WHERE c.id = ? 26 | ` 27 | return db.prepare(sql) 28 | .values(params.clientId) 29 | .map(data => mapArrayToNested02Result(data)); 30 | } 31 | 32 | function mapArrayToNested02Result(data: any) { 33 | const result: Nested02Result = { 34 | id: data[0], 35 | id_2: data[1], 36 | address: data[2], 37 | id_3: data[3], 38 | address_2: data[4] 39 | } 40 | return result; 41 | } 42 | 43 | export type Nested02NestedC = { 44 | id: number; 45 | a1: Nested02NestedA1; 46 | a2?: Nested02NestedA2; 47 | } 48 | 49 | export type Nested02NestedA1 = { 50 | id: number; 51 | address: string; 52 | } 53 | 54 | export type Nested02NestedA2 = { 55 | id: number; 56 | address: string; 57 | } 58 | 59 | export function nested02Nested(db: Database, params: Nested02Params): Nested02NestedC[] { 60 | const selectResult = nested02(db, params); 61 | if (selectResult.length == 0) { 62 | return []; 63 | } 64 | return collectNested02NestedC(selectResult); 65 | } 66 | 67 | function collectNested02NestedC(selectResult: Nested02Result[]): Nested02NestedC[] { 68 | const grouped = groupBy(selectResult.filter(r => r.id != null), r => r.id); 69 | return [...grouped.values()].map(row => ({ 70 | id: row[0].id!, 71 | a1: collectNested02NestedA1(row)[0], 72 | a2: collectNested02NestedA2(row)[0], 73 | })) 74 | } 75 | 76 | function collectNested02NestedA1(selectResult: Nested02Result[]): Nested02NestedA1[] { 77 | const grouped = groupBy(selectResult.filter(r => r.id_2 != null), r => r.id_2); 78 | return [...grouped.values()].map(row => ({ 79 | id: row[0].id_2!, 80 | address: row[0].address!, 81 | })) 82 | } 83 | 84 | function collectNested02NestedA2(selectResult: Nested02Result[]): Nested02NestedA2[] { 85 | const grouped = groupBy(selectResult.filter(r => r.id_3 != null), r => r.id_3); 86 | return [...grouped.values()].map(row => ({ 87 | id: row[0].id_3!, 88 | address: row[0].address_2!, 89 | })) 90 | } 91 | 92 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 93 | return array.reduce((map, value, index, array) => { 94 | const key = predicate(value, index, array); 95 | map.get(key)?.push(value) ?? map.set(key, [value]); 96 | return map; 97 | }, new Map()); 98 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/nested02-clients-with-addresses.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Nested02Params = { 4 | clientId: number; 5 | } 6 | 7 | export type Nested02Result = { 8 | id: number; 9 | id_2: number; 10 | address: string; 11 | id_3?: number; 12 | address_2?: string; 13 | } 14 | 15 | export function nested02(db: Database, params: Nested02Params): Nested02Result[] { 16 | const sql = ` 17 | -- @nested 18 | SELECT 19 | c.id, 20 | a1.*, 21 | a2.* 22 | FROM clients as c 23 | INNER JOIN addresses as a1 ON a1.id = c.primaryAddress 24 | LEFT JOIN addresses as a2 ON a2.id = c.secondaryAddress 25 | WHERE c.id = ? 26 | ` 27 | return db.prepare(sql) 28 | .raw(true) 29 | .all([params.clientId]) 30 | .map(data => mapArrayToNested02Result(data)); 31 | } 32 | 33 | function mapArrayToNested02Result(data: any) { 34 | const result: Nested02Result = { 35 | id: data[0], 36 | id_2: data[1], 37 | address: data[2], 38 | id_3: data[3], 39 | address_2: data[4] 40 | } 41 | return result; 42 | } 43 | 44 | export type Nested02NestedC = { 45 | id: number; 46 | a1: Nested02NestedA1; 47 | a2?: Nested02NestedA2; 48 | } 49 | 50 | export type Nested02NestedA1 = { 51 | id: number; 52 | address: string; 53 | } 54 | 55 | export type Nested02NestedA2 = { 56 | id: number; 57 | address: string; 58 | } 59 | 60 | export function nested02Nested(db: Database, params: Nested02Params): Nested02NestedC[] { 61 | const selectResult = nested02(db, params); 62 | if (selectResult.length == 0) { 63 | return []; 64 | } 65 | return collectNested02NestedC(selectResult); 66 | } 67 | 68 | function collectNested02NestedC(selectResult: Nested02Result[]): Nested02NestedC[] { 69 | const grouped = groupBy(selectResult.filter(r => r.id != null), r => r.id); 70 | return [...grouped.values()].map(row => ({ 71 | id: row[0].id!, 72 | a1: collectNested02NestedA1(row)[0], 73 | a2: collectNested02NestedA2(row)[0], 74 | })) 75 | } 76 | 77 | function collectNested02NestedA1(selectResult: Nested02Result[]): Nested02NestedA1[] { 78 | const grouped = groupBy(selectResult.filter(r => r.id_2 != null), r => r.id_2); 79 | return [...grouped.values()].map(row => ({ 80 | id: row[0].id_2!, 81 | address: row[0].address!, 82 | })) 83 | } 84 | 85 | function collectNested02NestedA2(selectResult: Nested02Result[]): Nested02NestedA2[] { 86 | const grouped = groupBy(selectResult.filter(r => r.id_3 != null), r => r.id_3); 87 | return [...grouped.values()].map(row => ({ 88 | id: row[0].id_3!, 89 | address: row[0].address_2!, 90 | })) 91 | } 92 | 93 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 94 | return array.reduce((map, value, index, array) => { 95 | const key = predicate(value, index, array); 96 | map.get(key)?.push(value) ?? map.set(key, [value]); 97 | return map; 98 | }, new Map()); 99 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/nested02-d1-clients-with-addresses.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type Nested02Params = { 4 | clientId: number; 5 | } 6 | 7 | export type Nested02Result = { 8 | id: number; 9 | id_2: number; 10 | address: string; 11 | id_3?: number; 12 | address_2?: string; 13 | } 14 | 15 | export async function nested02(db: D1Database, params: Nested02Params): Promise { 16 | const sql = ` 17 | -- @nested 18 | SELECT 19 | c.id, 20 | a1.*, 21 | a2.* 22 | FROM clients as c 23 | INNER JOIN addresses as a1 ON a1.id = c.primaryAddress 24 | LEFT JOIN addresses as a2 ON a2.id = c.secondaryAddress 25 | WHERE c.id = ? 26 | ` 27 | return db.prepare(sql) 28 | .bind(params.clientId) 29 | .raw({ columnNames: false }) 30 | .then(rows => rows.map(row => mapArrayToNested02Result(row))); 31 | } 32 | 33 | function mapArrayToNested02Result(data: any) { 34 | const result: Nested02Result = { 35 | id: data[0], 36 | id_2: data[1], 37 | address: data[2], 38 | id_3: data[3], 39 | address_2: data[4] 40 | } 41 | return result; 42 | } 43 | 44 | export type Nested02NestedC = { 45 | id: number; 46 | a1: Nested02NestedA1; 47 | a2?: Nested02NestedA2; 48 | } 49 | 50 | export type Nested02NestedA1 = { 51 | id: number; 52 | address: string; 53 | } 54 | 55 | export type Nested02NestedA2 = { 56 | id: number; 57 | address: string; 58 | } 59 | 60 | export async function nested02Nested(db: D1Database, params: Nested02Params): Promise { 61 | const selectResult = await nested02(db, params); 62 | if (selectResult.length == 0) { 63 | return []; 64 | } 65 | return collectNested02NestedC(selectResult); 66 | } 67 | 68 | function collectNested02NestedC(selectResult: Nested02Result[]): Nested02NestedC[] { 69 | const grouped = groupBy(selectResult.filter(r => r.id != null), r => r.id); 70 | return [...grouped.values()].map(row => ({ 71 | id: row[0].id!, 72 | a1: collectNested02NestedA1(row)[0], 73 | a2: collectNested02NestedA2(row)[0], 74 | })) 75 | } 76 | 77 | function collectNested02NestedA1(selectResult: Nested02Result[]): Nested02NestedA1[] { 78 | const grouped = groupBy(selectResult.filter(r => r.id_2 != null), r => r.id_2); 79 | return [...grouped.values()].map(row => ({ 80 | id: row[0].id_2!, 81 | address: row[0].address!, 82 | })) 83 | } 84 | 85 | function collectNested02NestedA2(selectResult: Nested02Result[]): Nested02NestedA2[] { 86 | const grouped = groupBy(selectResult.filter(r => r.id_3 != null), r => r.id_3); 87 | return [...grouped.values()].map(row => ({ 88 | id: row[0].id_3!, 89 | address: row[0].address_2!, 90 | })) 91 | } 92 | 93 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 94 | return array.reduce((map, value, index, array) => { 95 | const key = predicate(value, index, array); 96 | map.get(key)?.push(value) ?? map.set(key, [value]); 97 | return map; 98 | }, new Map()); 99 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/nested03-bun-many-to-many.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Nested03Result = { 4 | surveyId: number; 5 | surveyName: string; 6 | userId: number; 7 | userName: string; 8 | } 9 | 10 | export function nested03(db: Database): Nested03Result[] { 11 | const sql = ` 12 | -- @nested 13 | SELECT 14 | s.id as surveyId, 15 | s.name as surveyName, 16 | u.id as userId, 17 | u.name as userName 18 | FROM surveys s 19 | INNER JOIN participants p on p.fk_survey = s.id 20 | INNER JOIN users u on u.id = p.fk_user 21 | ` 22 | return db.prepare(sql) 23 | .values() 24 | .map(data => mapArrayToNested03Result(data)); 25 | } 26 | 27 | function mapArrayToNested03Result(data: any) { 28 | const result: Nested03Result = { 29 | surveyId: data[0], 30 | surveyName: data[1], 31 | userId: data[2], 32 | userName: data[3] 33 | } 34 | return result; 35 | } 36 | 37 | export type Nested03NestedSurveys = { 38 | surveyId: number; 39 | surveyName: string; 40 | users: Nested03NestedUsers[]; 41 | } 42 | 43 | export type Nested03NestedUsers = { 44 | userId: number; 45 | userName: string; 46 | } 47 | 48 | export function nested03Nested(db: Database): Nested03NestedSurveys[] { 49 | const selectResult = nested03(db); 50 | if (selectResult.length == 0) { 51 | return []; 52 | } 53 | return collectNested03NestedSurveys(selectResult); 54 | } 55 | 56 | function collectNested03NestedSurveys(selectResult: Nested03Result[]): Nested03NestedSurveys[] { 57 | const grouped = groupBy(selectResult.filter(r => r.surveyId != null), r => r.surveyId); 58 | return [...grouped.values()].map(row => ({ 59 | surveyId: row[0].surveyId!, 60 | surveyName: row[0].surveyName!, 61 | users: collectNested03NestedUsers(row), 62 | })) 63 | } 64 | 65 | function collectNested03NestedUsers(selectResult: Nested03Result[]): Nested03NestedUsers[] { 66 | const grouped = groupBy(selectResult.filter(r => r.userId != null), r => r.userId); 67 | return [...grouped.values()].map(row => ({ 68 | userId: row[0].userId!, 69 | userName: row[0].userName!, 70 | })) 71 | } 72 | 73 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 74 | return array.reduce((map, value, index, array) => { 75 | const key = predicate(value, index, array); 76 | map.get(key)?.push(value) ?? map.set(key, [value]); 77 | return map; 78 | }, new Map()); 79 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/nested03-libsql-many-to-many.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Client, Transaction } from '@libsql/client'; 2 | 3 | export type Nested03Result = { 4 | surveyId: number; 5 | surveyName: string; 6 | userId: number; 7 | userName: string; 8 | } 9 | 10 | export async function nested03(client: Client | Transaction): Promise { 11 | const sql = ` 12 | -- @nested 13 | SELECT 14 | s.id as surveyId, 15 | s.name as surveyName, 16 | u.id as userId, 17 | u.name as userName 18 | FROM surveys s 19 | INNER JOIN participants p on p.fk_survey = s.id 20 | INNER JOIN users u on u.id = p.fk_user 21 | ` 22 | return client.execute(sql) 23 | .then(res => res.rows) 24 | .then(rows => rows.map(row => mapArrayToNested03Result(row))); 25 | } 26 | 27 | function mapArrayToNested03Result(data: any) { 28 | const result: Nested03Result = { 29 | surveyId: data[0], 30 | surveyName: data[1], 31 | userId: data[2], 32 | userName: data[3] 33 | } 34 | return result; 35 | } 36 | 37 | export type Nested03NestedSurveys = { 38 | surveyId: number; 39 | surveyName: string; 40 | users: Nested03NestedUsers[]; 41 | } 42 | 43 | export type Nested03NestedUsers = { 44 | userId: number; 45 | userName: string; 46 | } 47 | 48 | export async function nested03Nested(client: Client | Transaction): Promise { 49 | const selectResult = await nested03(client); 50 | if (selectResult.length == 0) { 51 | return []; 52 | } 53 | return collectNested03NestedSurveys(selectResult); 54 | } 55 | 56 | function collectNested03NestedSurveys(selectResult: Nested03Result[]): Nested03NestedSurveys[] { 57 | const grouped = groupBy(selectResult.filter(r => r.surveyId != null), r => r.surveyId); 58 | return [...grouped.values()].map(row => ({ 59 | surveyId: row[0].surveyId!, 60 | surveyName: row[0].surveyName!, 61 | users: collectNested03NestedUsers(row), 62 | })) 63 | } 64 | 65 | function collectNested03NestedUsers(selectResult: Nested03Result[]): Nested03NestedUsers[] { 66 | const grouped = groupBy(selectResult.filter(r => r.userId != null), r => r.userId); 67 | return [...grouped.values()].map(row => ({ 68 | userId: row[0].userId!, 69 | userName: row[0].userName!, 70 | })) 71 | } 72 | 73 | const groupBy = (array: T[], predicate: (value: T, index: number, array: T[]) => Q) => { 74 | return array.reduce((map, value, index, array) => { 75 | const key = predicate(value, index, array); 76 | map.get(key)?.push(value) ?? map.set(key, [value]); 77 | return map; 78 | }, new Map()); 79 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select01-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Select01Params = { 4 | param1: number; 5 | } 6 | 7 | export type Select01Result = { 8 | id: number; 9 | name?: string; 10 | } 11 | 12 | export function select01(db: Database, params: Select01Params): Select01Result | null { 13 | const sql = ` 14 | select id, name from mytable2 where id = ? 15 | ` 16 | const res = db.prepare(sql) 17 | .values(params.param1); 18 | 19 | return res.length > 0 ? mapArrayToSelect01Result(res[0]) : null; 20 | } 21 | 22 | function mapArrayToSelect01Result(data: any) { 23 | const result: Select01Result = { 24 | id: data[0], 25 | name: data[1] 26 | } 27 | return result; 28 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select01-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type Select01Params = { 4 | param1: number; 5 | } 6 | 7 | export type Select01Result = { 8 | id: number; 9 | name?: string; 10 | } 11 | 12 | export async function select01(db: D1Database, params: Select01Params): Promise { 13 | const sql = ` 14 | select id, name from mytable2 where id = ? 15 | ` 16 | return db.prepare(sql) 17 | .bind(params.param1) 18 | .raw({ columnNames: false }) 19 | .then(rows => rows.length > 0 ? mapArrayToSelect01Result(rows[0]) : null); 20 | } 21 | 22 | function mapArrayToSelect01Result(data: any) { 23 | const result: Select01Result = { 24 | id: data[0], 25 | name: data[1] 26 | } 27 | return result; 28 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select01-libsql.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Client, Transaction } from '@libsql/client'; 2 | 3 | export type Select01Params = { 4 | param1: number; 5 | } 6 | 7 | export type Select01Result = { 8 | id: number; 9 | name?: string; 10 | } 11 | 12 | export async function select01(client: Client | Transaction, params: Select01Params): Promise { 13 | const sql = ` 14 | select id, name from mytable2 where id = ? 15 | ` 16 | return client.execute({ sql, args: [params.param1] }) 17 | .then(res => res.rows) 18 | .then(rows => rows.length > 0 ? mapArrayToSelect01Result(rows[0]) : null); 19 | } 20 | 21 | function mapArrayToSelect01Result(data: any) { 22 | const result: Select01Result = { 23 | id: data[0], 24 | name: data[1] 25 | } 26 | return result; 27 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select01.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Select01Params = { 4 | param1: number; 5 | } 6 | 7 | export type Select01Result = { 8 | id: number; 9 | name?: string; 10 | } 11 | 12 | export function select01(db: Database, params: Select01Params): Select01Result | null { 13 | const sql = ` 14 | select id, name from mytable2 where id = ? 15 | ` 16 | const res = db.prepare(sql) 17 | .raw(true) 18 | .get([params.param1]); 19 | 20 | return res ? mapArrayToSelect01Result(res) : null; 21 | } 22 | 23 | function mapArrayToSelect01Result(data: any) { 24 | const result: Select01Result = { 25 | id: data[0], 26 | name: data[1] 27 | } 28 | return result; 29 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select02-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Select02Result = { 4 | id: number; 5 | } 6 | 7 | export function select02(db: Database): Select02Result[] { 8 | const sql = ` 9 | select id from mytable1 10 | ` 11 | return db.prepare(sql) 12 | .values() 13 | .map(data => mapArrayToSelect02Result(data)); 14 | } 15 | 16 | function mapArrayToSelect02Result(data: any) { 17 | const result: Select02Result = { 18 | id: data[0] 19 | } 20 | return result; 21 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select02-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type Select02Result = { 4 | id: number; 5 | } 6 | 7 | export async function select02(db: D1Database): Promise { 8 | const sql = ` 9 | select id from mytable1 10 | ` 11 | return db.prepare(sql) 12 | .raw({ columnNames: false }) 13 | .then(rows => rows.map(row => mapArrayToSelect02Result(row))); 14 | } 15 | 16 | function mapArrayToSelect02Result(data: any) { 17 | const result: Select02Result = { 18 | id: data[0] 19 | } 20 | return result; 21 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select02-libsql.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Client, Transaction } from '@libsql/client'; 2 | 3 | export type Select02Result = { 4 | id: number; 5 | } 6 | 7 | export async function select02(client: Client | Transaction): Promise { 8 | const sql = ` 9 | select id from mytable1 10 | ` 11 | return client.execute(sql) 12 | .then(res => res.rows) 13 | .then(rows => rows.map(row => mapArrayToSelect02Result(row))); 14 | } 15 | 16 | function mapArrayToSelect02Result(data: any) { 17 | const result: Select02Result = { 18 | id: data[0] 19 | } 20 | return result; 21 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select02.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Select02Result = { 4 | id: number; 5 | } 6 | 7 | export function select02(db: Database): Select02Result[] { 8 | const sql = ` 9 | select id from mytable1 10 | ` 11 | return db.prepare(sql) 12 | .raw(true) 13 | .all() 14 | .map(data => mapArrayToSelect02Result(data)); 15 | } 16 | 17 | function mapArrayToSelect02Result(data: any) { 18 | const result: Select02Result = { 19 | id: data[0] 20 | } 21 | return result; 22 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select03-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Select03Params = { 4 | id: number; 5 | } 6 | 7 | export type Select03Result = { 8 | id: number; 9 | } 10 | 11 | export function select03(db: Database, params: Select03Params): Select03Result[] { 12 | const sql = ` 13 | select id from mytable1 where id = ? or id = ? 14 | ` 15 | return db.prepare(sql) 16 | .values(params.id, params.id) 17 | .map(data => mapArrayToSelect03Result(data)); 18 | } 19 | 20 | function mapArrayToSelect03Result(data: any) { 21 | const result: Select03Result = { 22 | id: data[0] 23 | } 24 | return result; 25 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select03-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type Select03Params = { 4 | id: number; 5 | } 6 | 7 | export type Select03Result = { 8 | id: number; 9 | } 10 | 11 | export async function select03(db: D1Database, params: Select03Params): Promise { 12 | const sql = ` 13 | select id from mytable1 where id = ? or id = ? 14 | ` 15 | return db.prepare(sql) 16 | .bind(params.id, params.id) 17 | .raw({ columnNames: false }) 18 | .then(rows => rows.map(row => mapArrayToSelect03Result(row))); 19 | } 20 | 21 | function mapArrayToSelect03Result(data: any) { 22 | const result: Select03Result = { 23 | id: data[0] 24 | } 25 | return result; 26 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select03.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Select03Params = { 4 | id: number; 5 | } 6 | 7 | export type Select03Result = { 8 | id: number; 9 | } 10 | 11 | export function select03(db: Database, params: Select03Params): Select03Result[] { 12 | const sql = ` 13 | select id from mytable1 where id = ? or id = ? 14 | ` 15 | return db.prepare(sql) 16 | .raw(true) 17 | .all([params.id, params.id]) 18 | .map(data => mapArrayToSelect03Result(data)); 19 | } 20 | 21 | function mapArrayToSelect03Result(data: any) { 22 | const result: Select03Result = { 23 | id: data[0] 24 | } 25 | return result; 26 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select04-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Select04Params = { 4 | date: Date; 5 | date_time: Date; 6 | } 7 | 8 | export type Select04Result = { 9 | text_column?: string; 10 | date_text?: Date; 11 | datetime_text?: Date; 12 | integer_column?: number; 13 | date_integer?: Date; 14 | datetime_integer?: Date; 15 | } 16 | 17 | export function select04(db: Database, params: Select04Params): Select04Result[] { 18 | const sql = ` 19 | SELECT 20 | text_column, 21 | date(text_column) as date_text, 22 | datetime(text_column) as datetime_text, 23 | integer_column, 24 | date(integer_column, 'auto') as date_integer, 25 | datetime(integer_column, 'auto') as datetime_integer 26 | FROM all_types 27 | WHERE date(text_column) = ? 28 | AND date(integer_column, 'auto') = ? 29 | AND datetime(text_column) = ? 30 | AND datetime(integer_column, 'auto') = ? 31 | ` 32 | return db.prepare(sql) 33 | .values(params.date?.toISOString().split('T')[0], params.date?.toISOString().split('T')[0], params.date_time?.toISOString().split('.')[0].replace('T', ' '), params.date_time?.toISOString().split('.')[0].replace('T', ' ')) 34 | .map(data => mapArrayToSelect04Result(data)); 35 | } 36 | 37 | function mapArrayToSelect04Result(data: any) { 38 | const result: Select04Result = { 39 | text_column: data[0], 40 | date_text: data[1] != null ? new Date(data[1]) : data[1], 41 | datetime_text: data[2] != null ? new Date(data[2]) : data[2], 42 | integer_column: data[3], 43 | date_integer: data[4] != null ? new Date(data[4]) : data[4], 44 | datetime_integer: data[5] != null ? new Date(data[5]) : data[5] 45 | } 46 | return result; 47 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select04-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type Select04Params = { 4 | date: Date; 5 | date_time: Date; 6 | } 7 | 8 | export type Select04Result = { 9 | text_column?: string; 10 | date_text?: Date; 11 | datetime_text?: Date; 12 | integer_column?: number; 13 | date_integer?: Date; 14 | datetime_integer?: Date; 15 | } 16 | 17 | export async function select04(db: D1Database, params: Select04Params): Promise { 18 | const sql = ` 19 | SELECT 20 | text_column, 21 | date(text_column) as date_text, 22 | datetime(text_column) as datetime_text, 23 | integer_column, 24 | date(integer_column, 'auto') as date_integer, 25 | datetime(integer_column, 'auto') as datetime_integer 26 | FROM all_types 27 | WHERE date(text_column) = ? 28 | AND date(integer_column, 'auto') = ? 29 | AND datetime(text_column) = ? 30 | AND datetime(integer_column, 'auto') = ? 31 | ` 32 | return db.prepare(sql) 33 | .bind(params.date?.toISOString().split('T')[0], params.date?.toISOString().split('T')[0], params.date_time?.toISOString().split('.')[0].replace('T', ' '), params.date_time?.toISOString().split('.')[0].replace('T', ' ')) 34 | .raw({ columnNames: false }) 35 | .then(rows => rows.map(row => mapArrayToSelect04Result(row))); 36 | } 37 | 38 | function mapArrayToSelect04Result(data: any) { 39 | const result: Select04Result = { 40 | text_column: data[0], 41 | date_text: data[1] != null ? new Date(data[1]) : data[1], 42 | datetime_text: data[2] != null ? new Date(data[2]) : data[2], 43 | integer_column: data[3], 44 | date_integer: data[4] != null ? new Date(data[4]) : data[4], 45 | datetime_integer: data[5] != null ? new Date(data[5]) : data[5] 46 | } 47 | return result; 48 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select04.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Select04Params = { 4 | date: Date; 5 | date_time: Date; 6 | } 7 | 8 | export type Select04Result = { 9 | text_column?: string; 10 | date_text?: Date; 11 | datetime_text?: Date; 12 | integer_column?: number; 13 | date_integer?: Date; 14 | datetime_integer?: Date; 15 | } 16 | 17 | export function select04(db: Database, params: Select04Params): Select04Result[] { 18 | const sql = ` 19 | SELECT 20 | text_column, 21 | date(text_column) as date_text, 22 | datetime(text_column) as datetime_text, 23 | integer_column, 24 | date(integer_column, 'auto') as date_integer, 25 | datetime(integer_column, 'auto') as datetime_integer 26 | FROM all_types 27 | WHERE date(text_column) = ? 28 | AND date(integer_column, 'auto') = ? 29 | AND datetime(text_column) = ? 30 | AND datetime(integer_column, 'auto') = ? 31 | ` 32 | return db.prepare(sql) 33 | .raw(true) 34 | .all([params.date?.toISOString().split('T')[0], params.date?.toISOString().split('T')[0], params.date_time?.toISOString().split('.')[0].replace('T', ' '), params.date_time?.toISOString().split('.')[0].replace('T', ' ')]) 35 | .map(data => mapArrayToSelect04Result(data)); 36 | } 37 | 38 | function mapArrayToSelect04Result(data: any) { 39 | const result: Select04Result = { 40 | text_column: data[0], 41 | date_text: data[1] != null ? new Date(data[1]) : data[1], 42 | datetime_text: data[2] != null ? new Date(data[2]) : data[2], 43 | integer_column: data[3], 44 | date_integer: data[4] != null ? new Date(data[4]) : data[4], 45 | datetime_integer: data[5] != null ? new Date(data[5]) : data[5] 46 | } 47 | return result; 48 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select05-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Select05Params = { 4 | orderBy: [Select05OrderBy, 'asc' | 'desc'][]; 5 | } 6 | 7 | export type Select05Result = { 8 | id: number; 9 | } 10 | 11 | export function select05(db: Database, params: Select05Params): Select05Result[] { 12 | const sql = ` 13 | SELECT id FROM mytable1 ORDER BY ${escapeOrderBy(params.orderBy)} 14 | ` 15 | return db.prepare(sql) 16 | .values() 17 | .map(data => mapArrayToSelect05Result(data)); 18 | } 19 | 20 | function mapArrayToSelect05Result(data: any) { 21 | const result: Select05Result = { 22 | id: data[0] 23 | } 24 | return result; 25 | } 26 | 27 | const orderByFragments = { 28 | 'id': `id`, 29 | 'mytable1.id': `mytable1.id`, 30 | 'value': `value`, 31 | 'mytable1.value': `mytable1.value`, 32 | } as const; 33 | 34 | export type Select05OrderBy = keyof typeof orderByFragments; 35 | 36 | function escapeOrderBy(orderBy: Select05Params['orderBy']): string { 37 | return orderBy.map(order => `${orderByFragments[order[0]]} ${order[1] == 'desc' ? 'desc' : 'asc'}`).join(', '); 38 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select05-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type Select05Params = { 4 | orderBy: [Select05OrderBy, 'asc' | 'desc'][]; 5 | } 6 | 7 | export type Select05Result = { 8 | id: number; 9 | } 10 | 11 | export async function select05(db: D1Database, params: Select05Params): Promise { 12 | const sql = ` 13 | SELECT id FROM mytable1 ORDER BY ${escapeOrderBy(params.orderBy)} 14 | ` 15 | return db.prepare(sql) 16 | .raw({ columnNames: false }) 17 | .then(rows => rows.map(row => mapArrayToSelect05Result(row))); 18 | } 19 | 20 | function mapArrayToSelect05Result(data: any) { 21 | const result: Select05Result = { 22 | id: data[0] 23 | } 24 | return result; 25 | } 26 | 27 | const orderByFragments = { 28 | 'id': `id`, 29 | 'mytable1.id': `mytable1.id`, 30 | 'value': `value`, 31 | 'mytable1.value': `mytable1.value`, 32 | } as const; 33 | 34 | export type Select05OrderBy = keyof typeof orderByFragments; 35 | 36 | function escapeOrderBy(orderBy: Select05Params['orderBy']): string { 37 | return orderBy.map(order => `${orderByFragments[order[0]]} ${order[1] == 'desc' ? 'desc' : 'asc'}`).join(', '); 38 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select05.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Select05Params = { 4 | orderBy: [Select05OrderBy, 'asc' | 'desc'][]; 5 | } 6 | 7 | export type Select05Result = { 8 | id: number; 9 | } 10 | 11 | export function select05(db: Database, params: Select05Params): Select05Result[] { 12 | const sql = ` 13 | SELECT id FROM mytable1 ORDER BY ${escapeOrderBy(params.orderBy)} 14 | ` 15 | return db.prepare(sql) 16 | .raw(true) 17 | .all() 18 | .map(data => mapArrayToSelect05Result(data)); 19 | } 20 | 21 | function mapArrayToSelect05Result(data: any) { 22 | const result: Select05Result = { 23 | id: data[0] 24 | } 25 | return result; 26 | } 27 | 28 | const orderByFragments = { 29 | 'id': `id`, 30 | 'mytable1.id': `mytable1.id`, 31 | 'value': `value`, 32 | 'mytable1.value': `mytable1.value`, 33 | } as const; 34 | 35 | export type Select05OrderBy = keyof typeof orderByFragments; 36 | 37 | function escapeOrderBy(orderBy: Select05Params['orderBy']): string { 38 | return orderBy.map(order => `${orderByFragments[order[0]]} ${order[1] == 'desc' ? 'desc' : 'asc'}`).join(', '); 39 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select06-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Select06Params = { 4 | ids: number[]; 5 | names: string[]; 6 | } 7 | 8 | export type Select06Result = { 9 | id: number; 10 | } 11 | 12 | export function select06(db: Database, params: Select06Params): Select06Result[] { 13 | const sql = ` 14 | SELECT id 15 | FROM mytable2 16 | WHERE id IN (${params.ids.map(() => '?')}) 17 | AND name IN (${params.names.map(() => '?')}) 18 | ` 19 | return db.prepare(sql) 20 | .values(...params.ids, ...params.names) 21 | .map(data => mapArrayToSelect06Result(data)); 22 | } 23 | 24 | function mapArrayToSelect06Result(data: any) { 25 | const result: Select06Result = { 26 | id: data[0] 27 | } 28 | return result; 29 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select06-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type Select06Params = { 4 | ids: number[]; 5 | names: string[]; 6 | } 7 | 8 | export type Select06Result = { 9 | id: number; 10 | } 11 | 12 | export async function select06(db: D1Database, params: Select06Params): Promise { 13 | const sql = ` 14 | SELECT id 15 | FROM mytable2 16 | WHERE id IN (${params.ids.map(() => '?')}) 17 | AND name IN (${params.names.map(() => '?')}) 18 | ` 19 | return db.prepare(sql) 20 | .bind(...params.ids, ...params.names) 21 | .raw({ columnNames: false }) 22 | .then(rows => rows.map(row => mapArrayToSelect06Result(row))); 23 | } 24 | 25 | function mapArrayToSelect06Result(data: any) { 26 | const result: Select06Result = { 27 | id: data[0] 28 | } 29 | return result; 30 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select06.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Select06Params = { 4 | ids: number[]; 5 | names: string[]; 6 | } 7 | 8 | export type Select06Result = { 9 | id: number; 10 | } 11 | 12 | export function select06(db: Database, params: Select06Params): Select06Result[] { 13 | const sql = ` 14 | SELECT id 15 | FROM mytable2 16 | WHERE id IN (${params.ids.map(() => '?')}) 17 | AND name IN (${params.names.map(() => '?')}) 18 | ` 19 | return db.prepare(sql) 20 | .raw(true) 21 | .all([...params.ids, ...params.names]) 22 | .map(data => mapArrayToSelect06Result(data)); 23 | } 24 | 25 | function mapArrayToSelect06Result(data: any) { 26 | const result: Select06Result = { 27 | id: data[0] 28 | } 29 | return result; 30 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select07-fts.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Select07Params = { 4 | match: string; 5 | } 6 | 7 | export type Select07Result = { 8 | id?: any; 9 | name?: any; 10 | descr?: any; 11 | } 12 | 13 | export function select07(db: Database, params: Select07Params): Select07Result[] { 14 | const sql = ` 15 | SELECT 16 | id, 17 | name, 18 | descr 19 | FROM mytable2_fts 20 | WHERE mytable2_fts MATCH ? 21 | LIMIT 20 22 | ` 23 | return db.prepare(sql) 24 | .raw(true) 25 | .all([params.match]) 26 | .map(data => mapArrayToSelect07Result(data)); 27 | } 28 | 29 | function mapArrayToSelect07Result(data: any) { 30 | const result: Select07Result = { 31 | id: data[0], 32 | name: data[1], 33 | descr: data[2] 34 | } 35 | return result; 36 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select08-boolean.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Select08Params = { 4 | param1: boolean; 5 | param2: boolean | null; 6 | } 7 | 8 | export type Select08Result = { 9 | id: number; 10 | param1: boolean; 11 | param2?: boolean; 12 | } 13 | 14 | export function select08(db: Database, params: Select08Params): Select08Result[] { 15 | const sql = ` 16 | SELECT 17 | id, 18 | ? as param1, 19 | ? as param2 20 | FROM mytable1 21 | WHERE ? is true OR (? is true OR ? is null) 22 | ` 23 | return db.prepare(sql) 24 | .raw(true) 25 | .all([params.param1 != null ? Number(params.param1) : params.param1, params.param2 != null ? Number(params.param2) : params.param2, params.param1 != null ? Number(params.param1) : params.param1, params.param2 != null ? Number(params.param2) : params.param2, params.param2 != null ? Number(params.param2) : params.param2]) 26 | .map(data => mapArrayToSelect08Result(data)); 27 | } 28 | 29 | function mapArrayToSelect08Result(data: any) { 30 | const result: Select08Result = { 31 | id: data[0], 32 | param1: data[1] != null ? Boolean(data[1]) : data[1], 33 | param2: data[2] != null ? Boolean(data[2]) : data[2] 34 | } 35 | return result; 36 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select09-bun-enum.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Select09Params = { 4 | enum_value: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'; 5 | } 6 | 7 | export type Select09Result = { 8 | enum_column: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'; 9 | } 10 | 11 | export function select09(db: Database, params: Select09Params): Select09Result[] { 12 | const sql = ` 13 | SELECT 14 | enum_column 15 | FROM all_types 16 | where enum_column = ? 17 | ` 18 | return db.prepare(sql) 19 | .values(params.enum_value) 20 | .map(data => mapArrayToSelect09Result(data)); 21 | } 22 | 23 | function mapArrayToSelect09Result(data: any) { 24 | const result: Select09Result = { 25 | enum_column: data[0] 26 | } 27 | return result; 28 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select09-enum.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Select09Params = { 4 | enum_value: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'; 5 | } 6 | 7 | export type Select09Result = { 8 | enum_column: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'; 9 | } 10 | 11 | export function select09(db: Database, params: Select09Params): Select09Result[] { 12 | const sql = ` 13 | SELECT 14 | enum_column 15 | FROM all_types 16 | where enum_column = ? 17 | ` 18 | return db.prepare(sql) 19 | .raw(true) 20 | .all([params.enum_value]) 21 | .map(data => mapArrayToSelect09Result(data)); 22 | } 23 | 24 | function mapArrayToSelect09Result(data: any) { 25 | const result: Select09Result = { 26 | enum_column: data[0] 27 | } 28 | return result; 29 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/select09-libsql-enum.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Client, Transaction } from '@libsql/client'; 2 | 3 | export type Select09Params = { 4 | enum_value: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'; 5 | } 6 | 7 | export type Select09Result = { 8 | enum_column: 'x-small' | 'small' | 'medium' | 'large' | 'x-large'; 9 | } 10 | 11 | export async function select09(client: Client | Transaction, params: Select09Params): Promise { 12 | const sql = ` 13 | SELECT 14 | enum_column 15 | FROM all_types 16 | where enum_column = ? 17 | ` 18 | return client.execute({ sql, args: [params.enum_value] }) 19 | .then(res => res.rows) 20 | .then(rows => rows.map(row => mapArrayToSelect09Result(row))); 21 | } 22 | 23 | function mapArrayToSelect09Result(data: any) { 24 | const result: Select09Result = { 25 | enum_column: data[0] 26 | } 27 | return result; 28 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/update01-bun.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'bun:sqlite'; 2 | 3 | export type Update01Data = { 4 | param1: number | null; 5 | } 6 | 7 | export type Update01Params = { 8 | param1: number; 9 | } 10 | 11 | export type Update01Result = { 12 | changes: number; 13 | } 14 | 15 | export function update01(db: Database, data: Update01Data, params: Update01Params): Update01Result { 16 | const sql = ` 17 | UPDATE mytable1 SET value=? WHERE id=? 18 | ` 19 | return db.prepare(sql) 20 | .run(data.param1, params.param1) as Update01Result; 21 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/update01-d1.ts.txt: -------------------------------------------------------------------------------- 1 | import type { D1Database } from '@cloudflare/workers-types'; 2 | 3 | export type Update01Data = { 4 | param1: number | null; 5 | } 6 | 7 | export type Update01Params = { 8 | param1: number; 9 | } 10 | 11 | export type Update01Result = { 12 | changes: number; 13 | } 14 | 15 | export async function update01(db: D1Database, data: Update01Data, params: Update01Params): Promise { 16 | const sql = ` 17 | UPDATE mytable1 SET value=? WHERE id=? 18 | ` 19 | return db.prepare(sql) 20 | .bind(data.param1, params.param1) 21 | .run() 22 | .then(res => res.meta); 23 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/update01-libsql.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Client, Transaction } from '@libsql/client'; 2 | 3 | export type Update01Data = { 4 | param1: number | null; 5 | } 6 | 7 | export type Update01Params = { 8 | param1: number; 9 | } 10 | 11 | export type Update01Result = { 12 | rowsAffected: number; 13 | } 14 | 15 | export async function update01(client: Client | Transaction, data: Update01Data, params: Update01Params): Promise { 16 | const sql = ` 17 | UPDATE mytable1 SET value=? WHERE id=? 18 | ` 19 | return client.execute({ sql, args: [data.param1, params.param1] }) 20 | .then(res => mapArrayToUpdate01Result(res)); 21 | } 22 | 23 | function mapArrayToUpdate01Result(data: any) { 24 | const result: Update01Result = { 25 | rowsAffected: data.rowsAffected 26 | } 27 | return result; 28 | } -------------------------------------------------------------------------------- /tests/sqlite/expected-code/update01.ts.txt: -------------------------------------------------------------------------------- 1 | import type { Database } from 'better-sqlite3'; 2 | 3 | export type Update01Data = { 4 | param1: number | null; 5 | } 6 | 7 | export type Update01Params = { 8 | param1: number; 9 | } 10 | 11 | export type Update01Result = { 12 | changes: number; 13 | } 14 | 15 | export function update01(db: Database, data: Update01Data, params: Update01Params): Update01Result { 16 | const sql = ` 17 | UPDATE mytable1 SET value=? WHERE id=? 18 | ` 19 | return db.prepare(sql) 20 | .run([data.param1, params.param1]) as Update01Result; 21 | } -------------------------------------------------------------------------------- /tests/sqlite/sqlite-infer-not-null.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import { SchemaDef } from '../../src/types'; 3 | import { isLeft } from 'fp-ts/lib/Either'; 4 | import { parseSql } from '../../src/sqlite-query-analyzer/parser'; 5 | import { sqliteDbSchema } from '../mysql-query-analyzer/create-schema'; 6 | import type { ColumnInfo } from '../../src/mysql-query-analyzer/types'; 7 | 8 | describe('sqlite-infer-not-null.test', () => { 9 | it('select with left join', () => { 10 | const sql = ` 11 | select t1.id, t2.id, t1.value, t2.name 12 | from mytable1 t1 13 | left join mytable2 t2 on t1.id = t2.id; 14 | `; 15 | const result = parseSql(sql, sqliteDbSchema); 16 | if (isLeft(result)) { 17 | assert.fail(`Shouldn't return an error: ${result.left.description}`); 18 | } 19 | const actual = getColumnNullability(result.right.columns); 20 | 21 | const expected = [true, false, false, false]; 22 | 23 | assert.deepStrictEqual(actual, expected); 24 | }); 25 | 26 | it('select value from mytable1 where value >= 1', () => { 27 | const sql = 'select value from mytable1 where value >= 1'; 28 | 29 | const result = parseSql(sql, sqliteDbSchema); 30 | if (isLeft(result)) { 31 | assert.fail(`Shouldn't return an error: ${result.left.description}`); 32 | } 33 | const actual = getColumnNullability(result.right.columns); 34 | 35 | assert.deepStrictEqual(actual, [true]); 36 | }); 37 | 38 | it('select value from mytable1 where value <= 1', () => { 39 | const sql = 'select value from mytable1 where value <= 1'; 40 | 41 | const result = parseSql(sql, sqliteDbSchema); 42 | if (isLeft(result)) { 43 | assert.fail(`Shouldn't return an error: ${result.left.description}`); 44 | } 45 | const actual = getColumnNullability(result.right.columns); 46 | 47 | assert.deepStrictEqual(actual, [true]); 48 | }); 49 | }); 50 | 51 | function getColumnNullability(columns: ColumnInfo[]) { 52 | return columns.map((col) => col.notNull); 53 | } 54 | -------------------------------------------------------------------------------- /tests/sqlite/sqlite-load-extension.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { isLeft } from 'fp-ts/lib/Either'; 3 | import { createSqliteClient, explainSql } from '../../src/sqlite-query-analyzer/query-executor'; 4 | import { createLibSqlClient } from '../../src/drivers/libsql'; 5 | import { SchemaDef } from '../../src/types'; 6 | import { parseSql } from '../../src/sqlite-query-analyzer/parser'; 7 | import { sqliteDbSchema } from '../mysql-query-analyzer/create-schema'; 8 | 9 | describe('load-extension', () => { 10 | it('better-sqlite3 - load_extension uuid4', () => { 11 | 12 | const client = createSqliteClient('better-sqlite3', './mydb.db', [], ['./tests/ext/uuid.dll']); 13 | if (client.isErr()) { 14 | assert.fail(`Shouldn't return an Error`); 15 | } 16 | 17 | const clientType = client.value.type; 18 | if (clientType != 'better-sqlite3') { 19 | assert.fail(`Shouldn't return an Error`); 20 | } 21 | const explainSqlResult = explainSql(client.value.client, 'SELECT uuid4()'); 22 | 23 | if (isLeft(explainSqlResult)) { 24 | assert.fail(`Shouldn't return an Error`); 25 | } 26 | assert.deepStrictEqual(explainSqlResult.right, true); 27 | }); 28 | 29 | it('bun:sqlite - load_extension uuid4', () => { 30 | 31 | const client = createSqliteClient('bun:sqlite', './mydb.db', [], ['./tests/ext/uuid.dll']); 32 | if (client.isErr()) { 33 | assert.fail(`Shouldn't return an Error`); 34 | } 35 | 36 | const clientType = client.value.type; 37 | if (clientType != 'bun:sqlite') { 38 | assert.fail(`Shouldn't return an Error`); 39 | } 40 | const explainSqlResult = explainSql(client.value.client, 'SELECT uuid4()'); 41 | 42 | if (isLeft(explainSqlResult)) { 43 | assert.fail(`Shouldn't return an Error`); 44 | } 45 | assert.deepStrictEqual(explainSqlResult.right, true); 46 | }); 47 | 48 | it('libsql - load_extension uuid4', () => { 49 | 50 | //C:\dev\typesql\tests\ext\uuid.dll 51 | const client = createLibSqlClient('./mydb.db', [], ['./tests/ext/uuid.dll'], 'authtoken'); 52 | if (client.isErr()) { 53 | assert.fail(`Shouldn't return an Error`); 54 | } 55 | 56 | const clientType = client.value.type; 57 | if (clientType != 'libsql') { 58 | assert.fail(`Shouldn't return an Error`); 59 | } 60 | const explainSqlResult = explainSql(client.value.client, 'SELECT uuid4()'); 61 | 62 | if (isLeft(explainSqlResult)) { 63 | assert.fail(`Shouldn't return an Error`); 64 | } 65 | assert.deepStrictEqual(explainSqlResult.right, true); 66 | }); 67 | 68 | it('select uuid4() as uuid4, uuid7() as uuid7', () => { 69 | const sql = 'select uuid4() as uuid4, uuid7() as uuid7'; 70 | 71 | const actual = parseSql(sql, sqliteDbSchema); 72 | const expected: SchemaDef = { 73 | sql, 74 | queryType: 'Select', 75 | multipleRowsResult: false, 76 | columns: [ 77 | { 78 | columnName: 'uuid4', 79 | type: 'TEXT', 80 | notNull: true, 81 | table: '' 82 | }, 83 | { 84 | columnName: 'uuid7', 85 | type: 'TEXT', 86 | notNull: true, 87 | table: '' 88 | } 89 | ], 90 | parameters: [] 91 | }; 92 | 93 | if (isLeft(actual)) { 94 | assert.fail(`Shouldn't return an error: ${actual.left.description}`); 95 | } 96 | assert.deepStrictEqual(actual.right, expected); 97 | }); 98 | }); -------------------------------------------------------------------------------- /tests/sqlite/sqlite-parse-delete.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import type { SchemaDef } from '../../src/types'; 3 | import { isLeft } from 'fp-ts/lib/Either'; 4 | import { parseSql } from '../../src/sqlite-query-analyzer/parser'; 5 | import { sqliteDbSchema } from '../mysql-query-analyzer/create-schema'; 6 | 7 | describe('sqlite-parse-delete', () => { 8 | it('delete from mytable1 where id = ?', () => { 9 | const sql = 'delete from mytable1 where id = ?'; 10 | 11 | const actual = parseSql(sql, sqliteDbSchema); 12 | const expected: SchemaDef = { 13 | sql: 'delete from mytable1 where id = ?', 14 | queryType: 'Delete', 15 | multipleRowsResult: false, 16 | columns: [], 17 | parameters: [ 18 | { 19 | name: 'param1', 20 | columnType: 'INTEGER', 21 | notNull: true 22 | } 23 | ] 24 | }; 25 | 26 | if (isLeft(actual)) { 27 | assert.fail(`Shouldn't return an error: ${actual.left.description}`); 28 | } 29 | assert.deepStrictEqual(actual.right, expected); 30 | }); 31 | 32 | it('delete from mytable1 where id = :id', () => { 33 | const sql = 'delete from mytable1 where id = :id'; 34 | 35 | const actual = parseSql(sql, sqliteDbSchema); 36 | const expected: SchemaDef = { 37 | sql: 'delete from mytable1 where id = ?', 38 | queryType: 'Delete', 39 | multipleRowsResult: false, 40 | columns: [], 41 | parameters: [ 42 | { 43 | name: 'id', 44 | columnType: 'INTEGER', 45 | notNull: true 46 | } 47 | ] 48 | }; 49 | 50 | if (isLeft(actual)) { 51 | assert.fail(`Shouldn't return an error: ${actual.left.description}`); 52 | } 53 | assert.deepStrictEqual(actual.right, expected); 54 | }); 55 | 56 | it('delete from mytable1 where value = 0 or value is null', () => { 57 | const sql = 'delete from mytable1 where value = 0 or value is null'; 58 | const actual = parseSql(sql, sqliteDbSchema); 59 | const expected: SchemaDef = { 60 | sql: 'delete from mytable1 where value = 0 or value is null', 61 | queryType: 'Delete', 62 | multipleRowsResult: false, 63 | columns: [], 64 | parameters: [] 65 | }; 66 | 67 | if (isLeft(actual)) { 68 | assert.fail(`Shouldn't return an error: ${actual.left.description}`); 69 | } 70 | assert.deepStrictEqual(actual.right, expected); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /tests/sqlite/sqlite-parse-params.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import { type ParameterDef, SchemaDef } from '../../src/types'; 3 | import { isLeft } from 'fp-ts/lib/Either'; 4 | import { parseSql } from '../../src/sqlite-query-analyzer/parser'; 5 | import { sqliteDbSchema } from '../mysql-query-analyzer/create-schema'; 6 | 7 | describe('sqlite-parse-params', () => { 8 | it('SELECT * from mytable1 where id > ?', async () => { 9 | const sql = ` 10 | SELECT * from mytable1 where id > ? 11 | `; 12 | const actual = await parseSql(sql, sqliteDbSchema); 13 | const expected: ParameterDef[] = [ 14 | { 15 | name: 'param1', 16 | columnType: 'INTEGER', 17 | notNull: true 18 | } 19 | ]; 20 | if (isLeft(actual)) { 21 | assert.fail(`Shouldn't return an error: ${actual.left.description}`); 22 | } 23 | assert.deepStrictEqual(actual.right.parameters, expected); 24 | }); 25 | 26 | it('SELECT * from mytable2 where id = ? or id > ?', async () => { 27 | const sql = ` 28 | SELECT * from mytable2 where id = ? or id > ? 29 | `; 30 | const actual = await parseSql(sql, sqliteDbSchema); 31 | const expected: ParameterDef[] = [ 32 | { 33 | name: 'param1', 34 | columnType: 'INTEGER', 35 | notNull: true 36 | }, 37 | { 38 | name: 'param2', 39 | columnType: 'INTEGER', 40 | notNull: true 41 | } 42 | ]; 43 | if (isLeft(actual)) { 44 | assert.fail(`Shouldn't return an error: ${actual.left.description}`); 45 | } 46 | assert.deepStrictEqual(actual.right.parameters, expected); 47 | }); 48 | 49 | it('select concat(?, ?) from mytable2', () => { 50 | const sql = ` 51 | select concat(?, ?) from mytable2 52 | `; 53 | const actual = parseSql(sql, sqliteDbSchema); 54 | const expected: ParameterDef[] = [ 55 | { 56 | name: 'param1', 57 | columnType: 'TEXT', 58 | notNull: true 59 | }, 60 | { 61 | name: 'param2', 62 | columnType: 'TEXT', 63 | notNull: true 64 | } 65 | ]; 66 | if (isLeft(actual)) { 67 | assert.fail(`Shouldn't return an error: ${actual.left.description}`); 68 | } 69 | assert.deepStrictEqual(actual.right.parameters, expected); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /tests/sqlite/sqlite-teste-replace-list-params.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import { replaceListParams } from '../../src/sqlite-query-analyzer/replace-list-params'; 3 | import type { ParameterNameAndPosition } from '../../src/types'; 4 | 5 | describe('sqlite-teste-replace-list-params', () => { 6 | it('SELECT id FROM mytable1 WHERE id in (?)', async () => { 7 | const sql = 'SELECT id FROM mytable1 WHERE id in (?)'; 8 | const newSql = replaceListParams(sql, getParamIndices(sql)); 9 | 10 | const expected = "SELECT id FROM mytable1 WHERE id in (${params.param1.map(() => '?')})"; 11 | 12 | assert.deepStrictEqual(newSql, expected); 13 | }); 14 | 15 | it('multiples parameters', async () => { 16 | const sql = 'SELECT id FROM mytable1 WHERE id in (?) and value in (1, 2, ?)'; 17 | const newSql = replaceListParams(sql, getParamIndices(sql)); 18 | 19 | const expected = 20 | "SELECT id FROM mytable1 WHERE id in (${params.param1.map(() => '?')}) and value in (1, 2, ${params.param2.map(() => '?')})"; 21 | 22 | assert.deepStrictEqual(newSql, expected); 23 | }); 24 | 25 | it('multiple lines', async () => { 26 | const sql = `SELECT id FROM mytable1 27 | WHERE id in (?) 28 | and value in (1, 2, ?)`; 29 | const newSql = replaceListParams(sql, getParamIndices(sql)); 30 | 31 | const expected = `SELECT id FROM mytable1 32 | WHERE id in (\${params.param1.map(() => \'?\')}) 33 | and value in (1, 2, \${params.param2.map(() => \'?\')})`; 34 | 35 | assert.deepStrictEqual(newSql, expected); 36 | }); 37 | }); 38 | 39 | function getParamIndices(sql: string): ParameterNameAndPosition[] { 40 | const indices: ParameterNameAndPosition[] = []; 41 | for (let i = 0; i < sql.length; i++) { 42 | if (sql[i] === '?') { 43 | indices.push({ 44 | name: `param${indices.length + 1}`, 45 | paramPosition: i 46 | }); 47 | } 48 | } 49 | return indices; 50 | } 51 | -------------------------------------------------------------------------------- /typesql-deno.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wsporto/typesql/6043ed7404bef17d2365e9109e3341d76de45c8d/typesql-deno.gif -------------------------------------------------------------------------------- /typesql-language-server.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wsporto/typesql/6043ed7404bef17d2365e9109e3341d76de45c8d/typesql-language-server.gif -------------------------------------------------------------------------------- /typesql.json: -------------------------------------------------------------------------------- 1 | { 2 | "databaseUri": "./mydb.db", 3 | "sqlDir": "./tests/sqlite-e2e/sql", 4 | "client": "better-sqlite3", 5 | "includeCrudTables": [] 6 | } -------------------------------------------------------------------------------- /typesql.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "databaseUri": "mysql://root:password@localhost/mydb", 3 | "sqlDir": "./sqls", 4 | "client": "mysql2", 5 | "includeCrudTables": [] 6 | } --------------------------------------------------------------------------------